4.0 KiB
4.0 KiB
Testing Lambda Functions
Complete guide for testing NestJS Lambda handlers including unit tests, integration tests, and mocking strategies.
Unit Tests for Handler
Test the Lambda handler in isolation with mocked AWS Lambda context.
// lambda.spec.ts
import { handler } from './lambda';
import { Context } from 'aws-lambda';
describe('Lambda Handler', () => {
const mockContext: Partial<Context> = {
functionName: 'test-function',
memoryLimitInMB: '512',
invokedFunctionArn: 'arn:aws:lambda:us-east-1:123456789:function:test',
awsRequestId: 'test-request-id',
};
beforeEach(() => {
// Reset cached server for cold start tests
jest.resetModules();
});
it('should bootstrap on first invocation (cold start)', async () => {
const event = {
httpMethod: 'GET',
path: '/api/health',
headers: {},
body: null,
};
const result = await handler(event, mockContext as Context, () => {});
expect(result.statusCode).toBe(200);
});
it('should reuse instance on warm invocation', async () => {
const event = {
httpMethod: 'GET',
path: '/api/health',
headers: {},
body: null,
};
// First invocation
await handler(event, mockContext as Context, () => {});
// Second invocation (should use cached server)
const start = Date.now();
const result = await handler(event, mockContext as Context, () => {});
const duration = Date.now() - start;
expect(result.statusCode).toBe(200);
expect(duration).toBeLessThan(100); // Warm start should be fast
});
});
Integration Test with serverless-offline
Test the full request/response cycle using the serverless-offline plugin.
// test/lambda.integration.spec.ts
describe('Lambda Integration', () => {
let server: any;
beforeAll(async () => {
// Bootstrap for local testing
const { bootstrap } = await import('../lambda');
server = await bootstrap();
});
afterAll(async () => {
if (server) {
// Cleanup
}
});
it('should handle API Gateway events', async () => {
const event = {
httpMethod: 'POST',
path: '/api/users',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Test User' }),
};
const result = await server(event, {});
expect(result.statusCode).toBe(201);
expect(JSON.parse(result.body)).toHaveProperty('id');
});
});
Mocking Strategies
Mock AWS Services
// __mocks__/aws-sdk.ts
export const mockSend = jest.fn();
export const DynamoDBClient = jest.fn(() => ({
send: mockSend,
}));
export const GetItemCommand = jest.fn();
export const PutItemCommand = jest.fn();
Mock NestJS Services
// test/mocks/services.mock.ts
export const createMockUserService = () => ({
findById: jest.fn(),
create: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
});
// In test
const mockUserService = createMockUserService();
mockUserService.findById.mockResolvedValue({ id: '123', name: 'Test' });
Mock Lambda Context
// test/utils/lambda-context.ts
import { Context } from 'aws-lambda';
export function createMockContext(overrides?: Partial<Context>): Context {
return {
awsRequestId: 'test-request-id',
functionName: 'test-function',
memoryLimitInMB: '512',
invokedFunctionArn: 'arn:aws:lambda:us-east-1:123456789:function:test',
getRemainingTimeInMillis: () => 30000,
done: () => {},
fail: () => {},
succeed: () => {},
...overrides,
} as Context;
}
Best Practices for Testing
- Reset module cache between tests to test cold/warm start behavior
- Mock external services (DynamoDB, S3, etc.) to avoid real AWS calls
- Test both cold and warm starts to verify caching works correctly
- Use realistic event payloads matching API Gateway format
- Measure timing for warm invocations to detect performance regressions
- Test error scenarios including timeouts and service failures