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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions