Testland
Browse all skills & agents

serverless-integration-test-builder

Workflow-driven skill that builds the integration-test suite for a serverless application from its IaC definition (SAM template / serverless.yml / Wrangler config / Vercel functions / Netlify functions). Walks through: identifying the function inventory + event sources, picking the right local-emulator per function (sam local / Miniflare / netlify dev / vercel dev / serverless-offline), generating test events per event source, asserting on cold-start + timeout budgets, and emitting the test directory + CI config. Use when introducing integration tests to a serverless project. Composes cold-start-budget-reference + lambda-timeout-budget-reference + per-platform S1s.

serverless-integration-test-builder

Overview

Serverless integration tests are awkward because the test surface is multi-tool: each platform has its own local emulator, event-source binding model, and cold-start characteristic. This skill produces a coherent test directory from the IaC definition.

The output: a test directory layout + per-function test files

  • CI config.

When to use

  • New serverless project; need integration test coverage.
  • Inherited a serverless project with no tests; need to build coverage.
  • Migrating between platforms (e.g., Lambda → Workers); need to port test discipline.

Step 1 - Inventory functions from IaC

Per platform:

PlatformIaC sourceWhere functions live
AWS SAMtemplate.yaml AWS::Serverless::FunctionResources.<name>
Serverless Frameworkserverless.yml functions:each entry
Cloudflare Workerswrangler.toml + script bindings[[durable_objects.bindings]], scripts
Vercelpages/api/ + app/api/ + middlewarefilesystem-derived
Netlifynetlify/functions/, netlify/edge-functions/filesystem-derived
# Example: SAM
yq '.Resources | to_entries | map(select(.value.Type == "AWS::Serverless::Function")) | .[].key' template.yaml

# Example: Serverless Framework
yq '.functions | keys' serverless.yml

Output: a function inventory:

functions:
  - name: get-user
    platform: aws-sam
    runtime: nodejs20.x
    handler: src/get-user.handler
    timeout: 10
    memory: 512
    events:
      - type: api
        path: /users/{id}
        method: GET
  - name: process-upload
    platform: aws-sam
    runtime: python3.12
    timeout: 300
    events:
      - type: s3
        bucket: uploads
        events: ['s3:ObjectCreated:*']

Step 2 - Pick the emulator per function

Default: pick one emulator per platform detected in Step 1's inventory - the per-platform skill below is the canonical driver for that runtime. Use multiple emulators only when the project mixes platforms (e.g., Vercel app

  • AWS Lambdas).
PlatformTest pathSkill
AWS Lambda (Node/Python/etc.)sam local invoke or direct handleraws-sam-local-testing
AWS Lambda (.NET)Amazon.Lambda.TestUtilitieslambda-test-tools-net
Cloudflare Workersvitest-pool-workers / Miniflarecloudflare-workers-miniflare
Vercel Edge@edge-runtime/jest-environmentvercel-edge-runtime-testing
Netlify Functionsnetlify dev + direct handlernetlify-functions-test
Serverless Frameworkserverless-offline / directserverless-framework-test-plugin

Step 3 - Generate test events per event source

Event sourceTool
API Gateway requestsam local generate-event apigateway aws-proxy
S3 object createdsam local generate-event s3 put
SQS messagesam local generate-event sqs receive-message
DynamoDB Streamssam local generate-event dynamodb update
EventBridgesam local generate-event events
Cloudflare Workers fetchnew Request('https://...') directly
Vercel/Netlifyconstruct Request object

Commit events to tests/fixtures/events/. Reference in tests.

Step 4 - Emit test scaffold per function

tests/
  integration/
    get-user.test.ts
    process-upload.test.ts
  fixtures/
    events/
      api-get-users-id.json
      s3-object-created.json
    payloads/
      user-1.json
  conftest.py             # Or jest setup
  README.md               # How to run + matrix

Per-function test:

// tests/integration/get-user.test.ts
import { handler } from '../../src/get-user';
import event from '../fixtures/events/api-get-users-id.json';

describe('get-user', () => {
  test('returns 200 for valid id', async () => {
    const response = await handler({ ...event, pathParameters: { id: '1' } } as any, {} as any);
    expect(response.statusCode).toBe(200);
  });

  test('returns 404 for missing id', async () => {
    const response = await handler({ ...event, pathParameters: { id: 'missing' } } as any, {} as any);
    expect(response.statusCode).toBe(404);
  });

  test('completes within timeout budget', async () => {
    // Per lambda-timeout-budget-reference: timeout=10s; want headroom
    const start = Date.now();
    await handler({ ...event, pathParameters: { id: '1' } } as any, {} as any);
    const elapsed = Date.now() - start;
    expect(elapsed).toBeLessThan(5000);  // 50% of timeout
  });
});

Step 5 - Cold-start + timeout budget assertions

For each function, derive a budget from cold-start-budget-reference

budgets:
  get-user:
    cold_start_p99_ms: 800       # AWS Lambda Node@512MB typical
    warm_p99_ms: 200
    timeout_s: 10
    integration_timeout: api-gateway-29s   # API GW < 29s

  process-upload:
    cold_start_p99_ms: 1500
    warm_p99_ms: 2000
    timeout_s: 300
    integration_timeout: s3-event-async    # No upstream limit

These become assertion thresholds in staging-smoke tests (run against deployed functions, not local).

Step 6 - CI integration

# .github/workflows/serverless-tests.yml
jobs:
  local-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v5
      - uses: actions/setup-node@v4
      - uses: aws-actions/setup-sam@v2
      - run: sam build --use-container
      - run: npm ci && npx jest tests/integration/

  staging-deploy-and-smoke:
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    needs: local-tests
    steps:
      - uses: actions/checkout@v5
      - run: sam deploy --stack-name staging --parameter-overrides Stage=staging
      - run: npx jest tests/staging-smoke/

Two-tier: local-fast tests on every PR; deploy + smoke on main.

Step 7 - Document coverage matrix

# Serverless Integration Test Matrix

## Functions covered

| Function | Local test | Cold-start test | Timeout test | Event sources |
|---|---|---|---|---|
| get-user | ✅ tests/integration/get-user.test.ts | staging only | ✅ <5s | API GW GET /users/{id} |
| process-upload | ✅ | staging only | ✅ <150s of 300s | S3 ObjectCreated |
| ... | | | | |

## Gaps

- `legacy-export` — no integration tests; on backlog.
- Cold-start tests not feasible locally for any function (per [`cold-start-budget-reference`](../cold-start-budget-reference/SKILL.md)).

## How to add a new function

1. Add to IaC.
2. Generate event fixture.
3. Add test file following the template.
4. Update this matrix.

Anti-patterns

Anti-patternWhy it failsFix
One emulator for all functionsMixed-platform tests are awkward; some emulators miss platform-specific behaviourPer-platform emulator
No staging deploy + smoke testsLocal-pass + prod-fail gapTwo-tier CI
Hand-rolled event payloadsSchema drift; missed fieldsUse sam local generate-event or commit fixtures
Skip cold-start budget assertionsp99 spikes go unnoticedPer-function budget in staging smoke
Test directory mixed with prod codetests/ dir not in deploy artifactKeep separate
No README documenting matrixFuture maintainers can't add functions consistentlyStep 7 README
Inflate jest test timeouts to mask slow handlersHides performance regressionMatch jest timeout to function timeout
Mock AWS SDK in all testsTests pass; IAM permission bugs hideUse LocalStack or real test account

Output

This skill produces:

  • A function inventory (Step 1).
  • Per-function test files using the right emulator (Steps 2-4).
  • A budget matrix (Step 5).
  • A two-tier CI config (Step 6).
  • A coverage matrix README (Step 7).

References