aws-sam-local-testing
Wraps AWS SAM (Serverless Application Model) Local CLI for testing Lambda functions locally: `sam local invoke` (single invocation with event payload), `sam local start-api` (local API Gateway emulator), `sam local start-lambda` (local Lambda invoke endpoint for AWS SDK clients), and event-payload generation (`sam local generate-event`). For C#/.NET Lambdas, references/dotnet.md covers handler-direct testing with Amazon.Lambda.TestUtilities (TestLambdaContext, TestLambdaLogger) and the dotnet-lambda CLI. Use when testing Lambda + API Gateway + integrated AWS services locally.
Install with skills.sh (any agent)
npx skills add testland/qa --skill aws-sam-local-testingaws-sam-local-testing
Overview
AWS SAM Local is the canonical local-testing toolchain for AWS Lambda. Per docs.aws.amazon.com/serverless-application-model (opens in new window), it runs Lambdas in Docker containers locally with images that mirror the Lambda runtime - same Linux, same Node/Python/Java binaries, same handler-invocation contract.
When to use
Authoring
Install
brew install aws-sam-cli
sam --version # 1.x or higher
docker --version # Required for sam localProject structure
A SAM project has template.yaml declaring Lambda functions:
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Resources:
HelloFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: src/
Handler: app.handler
Runtime: python3.12
Timeout: 10
MemorySize: 512
Events:
Api:
Type: Api
Properties:
Path: /hello
Method: GETGenerate an event payload
Per SAM docs:
sam local generate-event apigateway aws-proxy --path /hello --method GET > event.json
sam local generate-event s3 put --bucket mybucket --key file.txt > s3-event.json
sam local generate-event sqs receive-message > sqs-event.jsonSingle invocation
sam local invoke HelloFunction --event event.jsonOutput: handler's return value, plus the simulated Lambda runtime log lines.
Local API Gateway
sam local start-api --port 3000Now curl http://localhost:3000/hello exercises the full API Gateway → Lambda routing.
Local Lambda invoke endpoint
sam local start-lambda --port 3001Then point AWS SDK clients at http://localhost:3001:
import boto3
lambda_client = boto3.client('lambda', endpoint_url='http://localhost:3001', region_name='us-east-1')
lambda_client.invoke(FunctionName='HelloFunction', Payload=b'{}')Useful for testing Lambda → Lambda invocations end-to-end.
.NET Lambdas - handler-direct testing
For C#/.NET Lambda handlers, prefer in-process handler-direct invocation with Amazon.Lambda.TestUtilities (TestLambdaContext, TestLambdaLogger) over per-test sam local invoke spawns - setup, worked tests, and the dotnet-lambda CLI are in references/dotnet.md.
Integration with pytest
import subprocess, json
def invoke_lambda(name, event):
proc = subprocess.run(
["sam", "local", "invoke", name, "--event", "-"],
input=json.dumps(event), text=True, capture_output=True,
)
return json.loads(proc.stdout)
def test_hello():
result = invoke_lambda("HelloFunction", {"name": "world"})
assert result["statusCode"] == 200
assert "Hello, world" in result["body"]Running
sam build # Package Lambdas
sam local invoke HelloFunction --event event.jsonFor watch-mode:
sam build --use-container --watchCI integration
jobs:
sam-local-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: aws-actions/setup-sam@v2
- run: sam build --use-container
- run: pytest tests/lambda/Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
Skip sam build between code changes | Stale package; old code runs | sam build (or watch mode) |
sam local invoke for full-suite | Spawn cost per invocation; slow | sam local start-lambda once, invoke many |
| Compare local timing to prod | Docker overhead; cold-start model differs per cold-start-budget-reference | Test correctness locally; latency in prod |
| No event-payload generation | Hand-rolled events miss fields | sam local generate-event |
| Mock AWS SDK calls locally | Tests pass but prod IAM / endpoints fail | Use LocalStack or test against real low-cost AWS account |
| Skip API Gateway routing test | Lambda alone passes; API GW integration breaks | sam local start-api |
| Hardcoded path in test | OS-specific | Use generated events |
Limitations
References
.NET Lambda testing - Amazon.Lambda.TestUtilities and the dotnet-lambda CLI
View source (opens in new window).NET Lambda testing - Amazon.Lambda.TestUtilities and the dotnet-lambda CLI
For C#/.NET Lambdas the fast path is handler-direct invocation with a mock context, not spawning sam local invoke per test. AWS's canonical toolkit is aws-lambda-dotnet (opens in new window): Amazon.Lambda.TestUtilities for library-level fakes plus the Mock Lambda Test Tool (opens in new window) UI for manual invocation.
Install
dotnet add package Amazon.Lambda.Core
dotnet add package Amazon.Lambda.Serialization.SystemTextJson
dotnet add package Amazon.Lambda.TestUtilities
dotnet add package Microsoft.NET.Test.Sdk
dotnet add package xunitHandler + TestLambdaContext
Per Amazon.Lambda.TestUtilities (opens in new window):
using Amazon.Lambda.Core;
using Amazon.Lambda.Serialization.SystemTextJson;
[assembly: LambdaSerializer(typeof(DefaultLambdaJsonSerializer))]
public class Functions
{
public string Handler(string input, ILambdaContext context)
{
context.Logger.LogLine($"Got input: {input}");
return input.ToUpper();
}
}using Amazon.Lambda.TestUtilities;
using Xunit;
[Fact]
public void Handler_Uppercases()
{
var context = new TestLambdaContext
{
FunctionName = "test-fn",
RemainingTime = TimeSpan.FromSeconds(30), // Logger auto-set to TestLambdaLogger
};
Assert.Equal("HELLO", new Functions().Handler("hello", context));
}
[Fact]
public void Handler_LogsInput()
{
var context = new TestLambdaContext();
new Functions().Handler("hi", context);
var logger = (TestLambdaLogger)context.Logger;
Assert.Contains("Got input: hi", logger.Buffer.ToString());
}Remaining-time behaviour
Set RemainingTime low to exercise timeout-aware handlers (the early-return pattern in the cold-start-budget-reference references/timeout-budgets.md):
var context = new TestLambdaContext { RemainingTime = TimeSpan.FromSeconds(3) };
var result = new Functions().Handler("work-that-takes-time", context);
Assert.Contains("partial", result);Serialised event payloads
var json = File.ReadAllText("Events/apigw-request.json"); // from sam local generate-event
var request = new DefaultLambdaJsonSerializer().Deserialize<APIGatewayProxyRequest>(json);
var response = new Functions().HandleApi(request, new TestLambdaContext());
Assert.Equal(200, response.StatusCode);dotnet-lambda CLI (deploy + invoke path)
Per aws-extensions-for-dotnet-cli (opens in new window):
dotnet tool install -g Amazon.Lambda.Tools
dotnet lambda invoke-function MyFunction --payload '"hello"'For local-only testing, prefer handler-direct invocation.
CI
jobs:
dotnet-lambda-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-dotnet@v4
with: { dotnet-version: '8.0.x' }
- run: dotnet restore
- run: dotnet test --no-build --verbosity normalAnti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
Spawn dotnet lambda invoke per test | Network calls; slow; rate-limited | Handler-direct invocation |
Skip TestLambdaContext | Null context fails at runtime | Always pass one |
RemainingTime = TimeSpan.MaxValue | Timeout logic never exercised | Realistic remaining time |
Hand-rolled APIGatewayProxyRequest | Schema drift | sam local generate-event fixture |
Missing [assembly: LambdaSerializer] | Runtime deserialization fails | Register the serializer in tests too |
| Mocking the Logger | Loses TestLambdaLogger.Buffer replay | Use the default TestLambdaContext logger |
Limitations
References
Related skills
azure-functions-tests
Runs Azure Functions locally using Azure Functions Core Tools v4 (`func start`), Azurite storage emulation, and framework-native unit tests for handler code (.NET isolated worker model, Node.js v4, Python v2). Covers HTTP, queue, and timer trigger testing, admin-endpoint invocation for non-HTTP triggers, and binding verification via local.settings.json. Use when testing Azure Functions before deployment, reproducing trigger behaviour without live Azure services, or gating function handler logic in CI.
cloudflare-workers-miniflare
Wraps Miniflare 3 (the official Cloudflare Workers simulator) and Wrangler dev for testing Workers locally. Covers Miniflare's getMiniflare() programmatic API (workerd-backed simulation matching prod), the wrangler dev local-mode (live-reload during dev), KV / Durable Objects / R2 / D1 bindings emulation, and Vitest + @cloudflare/vitest-pool-workers for in-process tests. Use when testing Cloudflare Workers code locally.
cold-start-budget-reference
Pure-reference catalog of latency budgets across serverless runtimes: cold starts AND timeouts. Covers AWS Lambda's three-phase cold start (Init: download+unzip+runtime-bootstrap; Init code: imports + module load; Invoke: handler execution), Cloudflare Workers' isolate model (sub-millisecond cold starts via V8 isolates per developers.cloudflare.com), Vercel Edge Runtime, Lambda SnapStart for JVM (snapshot-restore for Java), and provisioned-concurrency trade-offs, plus Lambda timeout + billing budgets in references/timeout-budgets.md (the 15-minute hard limit, getRemainingTimeInMillis, per-ms billing, memory-CPU scaling, the API Gateway 29s / SQS visibility-timeout integration cascade). Use when designing latency or timeout budgets, choosing a runtime, sizing memory, or auditing cold-start variance in production.
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.