VOOZH about

URL: https://dev.to/hash01/serverless-typescript-testing-aws-48ab

⇱ 2- AWS Serverless: Testing (typescript) - DEV Community


Shifting from traditional application testing to serverless TypeScript engineering is all about shifting your perspective: you stop testing a running server, and you start testing how your function responds to events and SDK states. Here is a simple, practical example for each testing, using modern AWS SDK v3 syntax.


1. Unit Testing: Jest + Mock Payloads & SDK Client Mocking

In unit tests, you don't call real AWS services. You pass a simulated API Gateway event to your handler, and you mock the AWS SDK so it returns predictable data instead of hitting live infrastructure.

The Code Under Test (src/handler.ts)

import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, GetCommand } from '@aws-sdk/lib-dynamodb';

const ddbClient = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(ddbClient);

export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
 const userId = event.pathParameters?.id;

 if (!userId) {
 return { statusCode: 400, body: JSON.stringify({ message: 'Missing ID' }) };
 }

 // Fetch from DynamoDB
 const result = await docClient.send(new GetCommand({
 TableName: process.env.USERS_TABLE,
 Key: { id: userId }
 }));

 if (!result.Item) {
 return { statusCode: 404, body: JSON.stringify({ message: 'User not found' }) };
 }

 return {
 statusCode: 200,
 body: JSON.stringify(result.Item),
 };
};

The Jest Unit Test (tests/unit.test.ts)

Instead of the legacy aws-sdk-mock, the modern standard for AWS SDK v3 is aws-sdk-client-mock.

import { handler } from '../src/handler';
import { APIGatewayProxyEvent } from 'aws-lambda';
import { mockClient } from 'aws-sdk-client-mock';
import { DynamoDBDocumentClient, GetCommand } from '@aws-sdk/lib-dynamodb';

const ddbMock = mockClient(DynamoDBDocumentClient);

describe('Lambda Handler Unit Test', () => {
 beforeEach(() => {
 ddbMock.reset();
 process.env.USERS_TABLE = 'TestTable';
 });

 it('should return 200 and user data when user exists', async () => {
 // 1. Mock the AWS SDK Behavior
 ddbMock.on(GetCommand).resolves({
 Item: { id: 'user-123', name: 'Alice' }
 });

 // 2. Shape the Mocked APIGatewayProxyEvent Payload
 const mockEvent = {
 pathParameters: { id: 'user-123' }
 } as unknown as APIGatewayProxyEvent;

 // 3. Execute handler directly
 const response = await handler(mockEvent);

 expect(response.statusCode).toBe(200);
 expect(JSON.parse(response.body)).toEqual({ id: 'user-123', name: 'Alice' });
 });
});


2. Integration Testing: LocalStack

Integration tests verify that your code accurately interacts with auxiliary services, without relying on real AWS. LocalStack spins up containerized versions of AWS services inside your CI/CD pipeline or local environment.

To test against LocalStack, your application code just needs to point its AWS Clients to the local Docker container URL (usually http://localhost:4566).

The Integration Test Configuration (tests/integration.test.ts)

import { DynamoDBClient, CreateTableCommand } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, PutCommand } from '@aws-sdk/lib-dynamodb';
import { handler } from '../src/handler';
import { APIGatewayProxyEvent } from 'aws-lambda';

// Point the client to LocalStack instead of real AWS
const localStackConfig = {
 endpoint: 'http://localhost:4566',
 region: 'us-east-1',
 credentials: { accessKeyId: 'test', secretAccessKey: 'test' }
};

const ddbClient = new DynamoDBClient(localStackConfig);
const docClient = DynamoDBDocumentClient.from(ddbClient);

describe('DynamoDB Integration via LocalStack', () => {
 beforeAll(async () => {
 // Setup: Create the table in LocalStack before tests run
 await ddbClient.send(new CreateTableCommand({
 TableName: 'LocalUsersTable',
 AttributeDefinitions: [{ AttributeName: 'id', AttributeType: 'S' }],
 KeySchema: [{ AttributeName: 'id', KeyType: 'HASH' }],
 BillingMode: 'PAY_PER_REQUEST'
 }));

 process.env.USERS_TABLE = 'LocalUsersTable';
 });

 it('should successfully read data actually written to LocalStack', async () => {
 // Seed real data into the LocalStack DynamoDB container
 await docClient.send(new PutCommand({
 TableName: 'LocalUsersTable',
 Item: { id: 'user-456', name: 'Bob' }
 }));

 const mockEvent = { pathParameters: { id: 'user-456' } } as unknown as APIGatewayProxyEvent;

 // Run the handler β€” it will communicate directly with LocalStack
 const response = await handler(mockEvent);

 expect(response.statusCode).toBe(200);
 expect(JSON.parse(response.body).name).toBe('Bob');
 });
});


3. End-to-End (E2E) Testing: Newman

In E2E testing, your application is fully deployed to a staging environment. You treat it entirely as a black boxβ€”sending actual HTTP requests to the live API Gateway URL. Newman allows you to run Postman collections natively via the CLI.

The Testing Workflow

  1. You export a Postman collection containing your test assertions (my-api-tests.json).
  2. You execute it via your terminal or CI environment against the live URL.
# Execute your Postman test collection against your deployed AWS Staging endpoint
newman run ./tests/my-api-tests.json --env-var "baseUrl=https://xyz123.execute-api.us-east-1.amazonaws.com/staging"


4. Local Execution: AWS SAM Local

Before deploying to staging, you want to see how your Lambda executes inside an isolated Docker container mimicking the real AWS Lambda runtime environment. AWS SAM achieves this.

Step A: Define your event payload (events/mock-request.json)

{"pathParameters":{"id":"user-789"}}

Step B: Invoke the function via CLI

Run the SAM build command to compile your TypeScript down to JavaScript, then use sam local invoke to execute the function with your mock event.

# 1. Compile TypeScript and build resources
sam build

# 2. Locally invoke the function using the mock event
sam local invoke UserHandlerFunction --event events/mock-request.json


Hope youe find it helpful
Hash