Skip to content

feat(testing): add AwsLambda.Host.Testing package for integration testing #185

Description

@j-d-ha

📋 Story

As a developer using AwsLambda.Host, I want to write integration tests for my Lambda functions that test the full request/response cycle including middleware, similar to how WebApplicationFactory works in ASP.NET Core.

🎯 Goals

Create a new package AwsLambda.Host.Testing that:

  • Follows the WebApplicationFactory pattern from ASP.NET Core
  • Enables bootstrap-level testing by mocking the Lambda Runtime API
  • Supports invoking test events and capturing responses
  • Allows service overrides for testing scenarios
  • Explicitly supports testing custom middleware

💡 Design Approach

Core Architecture

The package will leverage the existing LambdaHostOptions.BootstrapHttpClient property to inject a custom HttpMessageHandler that mocks the AWS Lambda Runtime API HTTP endpoints:

  • GET /runtime/invocation/next - Returns queued test events
  • POST /runtime/invocation/{requestId}/response - Captures success responses
  • POST /runtime/invocation/{requestId}/error - Captures error responses

Key Components

  1. LambdaApplicationFactory<TProgram> - Main factory class

    • Generic parameter for program/entry point discovery
    • Virtual ConfigureServices() for service overrides
    • Virtual ConfigureApplication() for builder customization
    • Implements IAsyncDisposable for lifecycle management
  2. MockLambdaRuntimeApiHandler - Internal HTTP message handler

    • In-memory queue for test events (using System.Threading.Channels)
    • Captures responses using TaskCompletionSource
    • No external HTTP server needed
  3. InvocationResult / InvocationResult<TResponse> - Result wrappers

    • Success/error status
    • Raw JSON responses
    • Typed response deserialization
    • Duration tracking
    • Lambda context reference

Public API Design

// Basic usage
public class LambdaTests : IAsyncDisposable
{
    private readonly LambdaApplicationFactory<Program> _factory = new();

    [Fact]
    public async Task Handler_ReturnsExpectedResponse()
    {
        var result = await _factory.InvokeAsync<MyRequest, MyResponse>(
            new MyRequest { Name = "Test" }
        );

        result.IsSuccess.Should().BeTrue();
        result.Response!.Message.Should().Be("Hello Test");
    }

    public ValueTask DisposeAsync() => _factory.DisposeAsync();
}

// With service overrides
public class CustomFactory : LambdaApplicationFactory<Program>
{
    protected override void ConfigureServices(IServiceCollection services)
    {
        services.Replace(ServiceDescriptor.Singleton<IMyService, MockService>());
    }
}

// Raw JSON support
var result = await factory.InvokeAsync("""{"name": "Test"}""");

Invocation Methods

The factory will provide multiple invocation overloads:

  • InvokeAsync<TEvent, TResponse>(TEvent event) - Typed request and response
  • InvokeAsync<TEvent>(TEvent event) - Typed request, no response
  • InvokeAsync(string eventJson) - Raw JSON input

📦 Package Structure

src/AwsLambda.Host.Testing/
├── AwsLambda.Host.Testing.csproj
├── LambdaApplicationFactory.cs
├── Runtime/
│   ├── MockLambdaRuntimeApiHandler.cs
│   ├── MockInvocation.cs
│   └── InvocationResult.cs
└── Extensions/
    └── ServiceCollectionExtensions.cs (if needed)

tests/AwsLambda.Host.Testing.UnitTests/
└── LambdaApplicationFactoryTests.cs

✅ Acceptance Criteria

  • Factory follows WebApplicationFactory pattern (generic TProgram, ConfigureServices hook, IAsyncDisposable)
  • Supports bootstrap-level testing using mocked Lambda Runtime API
  • Provides typed event/response invocation methods
  • Provides raw JSON invocation method with automatic serialization
  • Allows service overrides via ConfigureServices()
  • Supports testing custom middleware in the invocation pipeline
  • Package targets .NET 8.0, 9.0, and 10.0
  • Comprehensive unit tests covering the factory itself
  • XML documentation on all public APIs
  • README with usage examples
  • Example test project demonstrating common patterns

🔍 Technical Notes

Integration Points

  • LambdaHostOptions.BootstrapHttpClient - Already exists (line 14 in LambdaHostOptions.cs), provides the injection point for test HTTP client
  • LambdaBootstrapAdapter - Already supports custom HttpClient (lines 35-42 in LambdaBootstrapAdapter.cs)
  • ILambdaSerializer - Use existing serializer from DI for event/response serialization

Key Files to Reference

  • /src/AwsLambda.Host/Runtime/LambdaBootstrapAdapter.cs - Bootstrap integration
  • /src/AwsLambda.Host/Core/Options/LambdaHostOptions.cs - Options pattern
  • /src/AwsLambda.Host/Builder/LambdaApplicationBuilder.cs - Builder pattern
  • /src/AwsLambda.Host/Runtime/LambdaHandlerComposer.cs - Handler pipeline composition
  • /tests/AwsLambda.Host.UnitTests/Builder/LambdaApplicationBuilderTests.cs - Testing patterns

Design Decisions

  1. Minimal Scope: Only provide the factory infrastructure - users bring their own assertion libraries (FluentAssertions, Shouldly, etc.)
  2. Bootstrap Level: Test the full lifecycle including init/shutdown handlers, not just the handler pipeline
  3. Raw JSON Support: Prioritize raw JSON input with generic serialization helper
  4. Middleware Support: Explicitly design for testing custom middleware

🎨 Future Enhancements (Not in Scope)

  • Assertion helpers for Lambda responses
  • Built-in mock ILambdaContext with fluent configuration
  • Support for concurrent invocations
  • Explicit init/shutdown testing APIs

📚 References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions