Testland
Browse all skills & agents

lambda-test-tools-net

Wraps Amazon.Lambda.TestTool (the canonical .NET Lambda local-testing toolkit from github.com/aws/aws-lambda-dotnet) for invoking Lambda handlers from xUnit / NUnit tests with simulated AWS Lambda contexts (ILambdaContext, ILambdaSerializer). Covers handler-direct invocation, mock context fixtures, the dotnet-lambda CLI, and integration with the .NET LambdaSerializer for JSON. Use when testing AWS Lambda functions written in C#/.NET. Composes cold-start-budget-reference + lambda-timeout-budget-reference.

lambda-test-tools-net

Overview

Amazon.Lambda.TestTool is AWS's canonical .NET package for testing Lambda functions. Per github.com/aws/aws-lambda-dotnet, it provides a Mock Lambda Test Tool (Windows / Mac / Linux UI to manually invoke Lambdas) plus library-level fakes for ILambdaContext.

For automated tests, the pattern is handler-direct invocation with a mock context.

When to use

  • xUnit / NUnit tests for .NET Lambda handlers.
  • Verifying handler logic without spinning up dotnet lambda invoke per test.
  • CI integration with deterministic, fast tests.

Authoring

Install

dotnet add package Amazon.Lambda.Core
dotnet add package Amazon.Lambda.Serialization.SystemTextJson
dotnet add package Amazon.Lambda.TestUtilities    # NUnit-friendly mocks
dotnet add package Microsoft.NET.Test.Sdk
dotnet add package xunit

Handler example

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();
    }
}

Test with TestLambdaContext

Per aws-lambda-dotnet Amazon.Lambda.TestUtilities:

using Amazon.Lambda.TestUtilities;
using Xunit;

public class FunctionsTests
{
    [Fact]
    public void Handler_Uppercases()
    {
        var functions = new Functions();
        var context = new TestLambdaContext
        {
            FunctionName = "test-fn",
            RemainingTime = TimeSpan.FromSeconds(30),
            // Logger is auto-set to TestLambdaLogger
        };

        var result = functions.Handler("hello", context);

        Assert.Equal("HELLO", result);
    }

    [Fact]
    public void Handler_LogsInput()
    {
        var functions = new Functions();
        var context = new TestLambdaContext();

        functions.Handler("hi", context);

        var logger = (TestLambdaLogger)context.Logger;
        Assert.Contains("Got input: hi", logger.Buffer.ToString());
    }
}

Testing remaining-time behaviour

Per lambda-timeout-budget-reference, handlers that check Context.RemainingTime:

[Fact]
public void Handler_EarlyReturnsWhenTimeLow()
{
    var functions = new Functions();
    var context = new TestLambdaContext
    {
        RemainingTime = TimeSpan.FromSeconds(3)  // Below 5s threshold
    };

    var result = functions.Handler("work-that-takes-time", context);

    Assert.Contains("partial", result);
}

Test with serialised event payloads

[Fact]
public void Handler_ParsesApiGatewayEvent()
{
    var json = File.ReadAllText("Events/apigw-request.json");
    var serializer = new DefaultLambdaJsonSerializer();
    var request = serializer.Deserialize<APIGatewayProxyRequest>(json);

    var functions = new Functions();
    var response = functions.HandleApi(request, new TestLambdaContext());

    Assert.Equal(200, response.StatusCode);
}

dotnet-lambda CLI (for the deploy + invoke path)

Per github.com/aws/aws-extensions-for-dotnet-cli:

dotnet tool install -g Amazon.Lambda.Tools

dotnet lambda invoke-function MyFunction --payload '"hello"'

For local-only testing, prefer the handler-direct pattern above.

Running

dotnet test

For watch-mode:

dotnet watch test

CI integration

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 normal

Anti-patterns

Anti-patternWhy it failsFix
Spawn dotnet lambda invoke per testNetwork calls; slow; rate-limitedHandler-direct invocation
Skip TestLambdaContextReal ILambdaContext is required; null failsAlways pass a context
RemainingTime = TimeSpan.MaxValueDoesn't exercise timeout logicSet realistic remaining time
Hand-rolled APIGatewayProxyRequestSchema driftGenerate via sam local generate-event or commit fixture
Test using prod IAMPermissions surprise in CIUse mock service clients
No assertion on logger outputLogger bugs slip throughInspect TestLambdaLogger.Buffer
Serializer not registeredLambdaSerializer attribute missing → runtime failsAdd assembly attribute
Mocking the LoggerLoses TestLambdaLogger's buffer-replay capabilityUse TestLambdaContext's default logger

Limitations

  • No JIT / native AoT difference. Local handler invocation uses standard .NET JIT; AOT-compiled Lambdas (ReadyToRun / Native AOT) behave differently. Pair with deployed-Lambda tests for production representativeness.
  • Doesn't test the runtime API. The Lambda runtime API (responsible for next / response polling) isn't invoked; bugs in handler-vs-runtime contract slip through.
  • Doesn't test cold-start budget per cold-start-budget-reference. In-process tests are warm; AOT cold-start savings invisible.
  • Custom serializer paths. If your handler uses a non-default JSON serializer, set up the assembly attribute identically in tests.

References