{"id":15651909,"url":"https://github.com/martincostello/lambda-test-server","last_synced_at":"2025-10-08T16:13:53.586Z","repository":{"id":35776976,"uuid":"219197305","full_name":"martincostello/lambda-test-server","owner":"martincostello","description":"A NuGet package that provides an in-memory test server for testing AWS Lambda functions","archived":false,"fork":false,"pushed_at":"2025-10-01T20:40:23.000Z","size":1604,"stargazers_count":33,"open_issues_count":2,"forks_count":2,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-10-01T22:21:03.375Z","etag":null,"topics":["aws","aws-lambda","netcore","testing"],"latest_commit_sha":null,"homepage":"https://blog.martincostello.com/integration-testing-lambda-with-dotnet-custom-runtime/","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/martincostello.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["martincostello"],"buy_me_a_coffee":"martincostello"}},"created_at":"2019-11-02T18:35:45.000Z","updated_at":"2025-10-01T20:40:25.000Z","dependencies_parsed_at":"2024-02-04T11:25:31.728Z","dependency_job_id":"08eb66cd-9023-4089-b3d1-781d501931db","html_url":"https://github.com/martincostello/lambda-test-server","commit_stats":{"total_commits":930,"total_committers":6,"mean_commits":155.0,"dds":0.5268817204301075,"last_synced_commit":"fa51ccb0b4d6d4c352d4f00f88c128e2b14a6561"},"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/martincostello/lambda-test-server","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martincostello%2Flambda-test-server","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martincostello%2Flambda-test-server/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martincostello%2Flambda-test-server/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martincostello%2Flambda-test-server/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/martincostello","download_url":"https://codeload.github.com/martincostello/lambda-test-server/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/martincostello%2Flambda-test-server/sbom","scorecard":{"id":669913,"data":{"date":"2025-08-21T16:55:11Z","repo":{"name":"github.com/martincostello/lambda-test-server","commit":"9f15e7236370b5cc422002e5c61f3e8f0804bf19"},"scorecard":{"version":"v5.2.1","commit":"ab2f6e92482462fe66246d9e32f642855a691dc1"},"score":8.2,"checks":[{"name":"Dependency-Update-Tool","score":10,"reason":"update tool detected","details":["Info: detected update tool: Dependabot: .github/dependabot.yml:1","Info: detected update tool: RenovateBot: .github/renovate.json:1"],"documentation":{"short":"Determines if the project uses a dependency update tool.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#dependency-update-tool"}},{"name":"Security-Policy","score":10,"reason":"security policy file detected","details":["Info: security policy file detected: SECURITY.md:1","Info: Found linked content: SECURITY.md:1","Info: Found disclosure, vulnerability, and/or timelines in security policy: SECURITY.md:1","Info: Found text in security policy: SECURITY.md:1"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#security-policy"}},{"name":"Code-Review","score":2,"reason":"Found 2/7 approved changesets -- score normalized to 2","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#binary-artifacts"}},{"name":"Maintained","score":10,"reason":"30 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":10,"reason":"GitHub workflow tokens follow principle of least privilege","details":["Warn: jobLevel 'contents' permission set to 'write': .github/workflows/build.yml:42","Info: jobLevel 'actions' permission set to 'read': .github/workflows/codeql.yml:22","Info: jobLevel 'contents' permission set to 'read': .github/workflows/codeql.yml:23","Info: jobLevel 'contents' permission set to 'read': .github/workflows/dependency-review.yml:17","Info: jobLevel 'contents' permission set to 'read': .github/workflows/lint.yml:35","Warn: jobLevel 'security-events' permission set to 'write': .github/workflows/lint.yml:36","Info: jobLevel 'actions' permission set to 'read': .github/workflows/lint.yml:34","Info: found token with 'none' permissions: .github/workflows/build.yml:1","Info: found token with 'none' permissions: .github/workflows/bump-version.yml:1","Info: found token with 'none' permissions: .github/workflows/codeql.yml:1","Info: found token with 'none' permissions: .github/workflows/dependency-review.yml:1","Info: found token with 'none' permissions: .github/workflows/lint.yml:1","Info: topLevel permissions set to 'read-all': .github/workflows/ossf-scorecard.yml:11","Info: found token with 'none' permissions: .github/workflows/release.yml:1"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":10,"reason":"all dependencies are pinned","details":["Info:  25 out of  25 GitHub-owned GitHubAction dependencies pinned","Info:  10 out of  10 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#pinned-dependencies"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#vulnerabilities"}},{"name":"CII-Best-Practices","score":5,"reason":"badge detected: Passing","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#cii-best-practices"}},{"name":"Signed-Releases","score":8,"reason":"4 out of the last 4 releases have a total of 4 signed artifacts.","details":["Info: signed release artifact: checksums.txt.sig: https://github.com/martincostello/lambda-test-server/releases/tag/v0.11.0","Info: signed release artifact: checksums.txt.sig: https://github.com/martincostello/lambda-test-server/releases/tag/v0.10.0","Info: signed release artifact: build-windows.spdx.json.sig: https://github.com/martincostello/lambda-test-server/releases/tag/v0.9.0","Info: signed release artifact: build-windows.spdx.json.sig: https://github.com/martincostello/lambda-test-server/releases/tag/v0.8.1","Warn: release artifact v0.11.0 does not have provenance: https://api.github.com/repos/martincostello/lambda-test-server/releases/240448166","Warn: release artifact v0.10.0 does not have provenance: https://api.github.com/repos/martincostello/lambda-test-server/releases/239728165","Warn: release artifact v0.9.0 does not have provenance: https://api.github.com/repos/martincostello/lambda-test-server/releases/184992795","Warn: release artifact v0.8.1 does not have provenance: https://api.github.com/repos/martincostello/lambda-test-server/releases/178651465"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#signed-releases"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#fuzzing"}},{"name":"SAST","score":10,"reason":"SAST tool is run on all commits","details":["Info: SAST configuration detected: CodeQL","Info: all commits (30) are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#sast"}},{"name":"Packaging","score":10,"reason":"packaging workflow detected","details":["Info: Project packages its releases by way of GitHub Actions.: .github/workflows/build.yml:202"],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#packaging"}},{"name":"Branch-Protection","score":3,"reason":"branch protection is not maximal on development and all release branches","details":["Info: 'allow deletion' disabled on branch 'main'","Info: 'force pushes' disabled on branch 'main'","Info: 'branch protection settings apply to administrators' is required to merge on branch 'main'","Warn: 'stale review dismissal' is disabled on branch 'main'","Warn: branch 'main' does not require approvers","Warn: codeowners review is not required on branch 'main'","Warn: 'last push approval' is disabled on branch 'main'","Warn: 'up-to-date branches' is disabled on branch 'main'","Info: status check found to merge onto on branch 'main'","Info: PRs are required in order to make changes on branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#branch-protection"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#license"}},{"name":"Contributors","score":10,"reason":"project has 8 contributing companies or organizations","details":["Info: found contributions from: App-vNext, aspnet-contrib, costellorg, dotnet, dotnet-foundation, grafana, msmvps, open-telemetry"],"documentation":{"short":"Determines if the project has a set of contributors from multiple organizations (e.g., companies).","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#contributors"}},{"name":"CI-Tests","score":10,"reason":"21 out of 21 merged PRs checked by a CI test -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project runs tests before pull requests are merged.","url":"https://github.com/ossf/scorecard/blob/ab2f6e92482462fe66246d9e32f642855a691dc1/docs/checks.md#ci-tests"}}]},"last_synced_at":"2025-08-21T19:37:29.044Z","repository_id":35776976,"created_at":"2025-08-21T19:37:29.044Z","updated_at":"2025-08-21T19:37:29.044Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278973852,"owners_count":26078316,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-08T02:00:06.501Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws","aws-lambda","netcore","testing"],"created_at":"2024-10-03T12:40:36.030Z","updated_at":"2025-10-08T16:13:53.577Z","avatar_url":"https://github.com/martincostello.png","language":"C#","funding_links":["https://github.com/sponsors/martincostello","https://buymeacoffee.com/martincostello"],"categories":[],"sub_categories":[],"readme":"# AWS Lambda Test Server for .NET\n\n[![NuGet][package-badge-version]][package-download]\n[![NuGet Downloads][package-badge-downloads]][package-download]\n\n[![Build status][build-badge]][build-status]\n[![codecov][coverage-badge]][coverage-report]\n[![OpenSSF Scorecard][scorecard-badge]][scorecard-report]\n\n## Introduction\n\nA NuGet package that builds on top of the `TestServer` class in the [Microsoft.AspNetCore.TestHost][testhost] NuGet package to provide infrastructure to use with end-to-end/integration tests of .NET 6 AWS Lambda Functions using a custom runtime with the `LambdaBootstrap` class from the [Amazon.Lambda.RuntimeSupport][lambda-runtime-support] NuGet package.\n\n[_.NET Core 3.0 on Lambda with AWS Lambda's Custom Runtime_][custom-lambda-runtime]\n\n### Installation\n\nTo install the library from [NuGet][package-download] using the .NET SDK run:\n\n```sh\ndotnet add package MartinCostello.Testing.AwsLambdaTestServer\n```\n\n### Usage\n\nBefore you can use the Lambda test server to test your function, you need to factor your function entry-point\nin such a way that you can supply both a `HttpClient` and `CancellationToken` to it from your tests. This is to allow you to both plug in the `HttpClient` for the test server into `LambdaBootstrap`, and to stop the Lambda function running at a time of your choosing by signalling the `CancellationToken`.\n\nHere's an example of how to do this with a simple Lambda function that takes an array of integers and returns them in reverse order:\n\n```csharp\nusing Amazon.Lambda.RuntimeSupport;\nusing Amazon.Lambda.Serialization.Json;\n\nnamespace MyFunctions;\n\npublic static class ReverseFunction\n{\n    public static async Task Main()\n        =\u003e await RunAsync();\n\n    public static async Task RunAsync(\n        HttpClient httpClient = null,\n        CancellationToken cancellationToken = default)\n    {\n        var serializer = new JsonSerializer();\n\n        using var handlerWrapper = HandlerWrapper.GetHandlerWrapper\u003cint[], int[]\u003e(ReverseAsync, serializer);\n        using var bootstrap = new LambdaBootstrap(httpClient ?? new HttpClient(), handlerWrapper);\n\n        await bootstrap.RunAsync(cancellationToken);\n    }\n\n    public static Task\u003cint[]\u003e ReverseAsync(int[] values)\n        =\u003e Task.FromResult(values.Reverse().ToArray());\n}\n```\n\nOnce you've done that, you can use `LambdaTestServer` in your tests with your function to verify how it processes requests.\n\nHere's an example using xunit to verify that `ReverseFunction` works as intended:\n\n```csharp\nusing System.Text.Json;\nusing MartinCostello.Testing.AwsLambdaTestServer;\nusing Xunit;\n\nnamespace MyFunctions;\n\npublic static class ReverseFunctionTests\n{\n    [Fact]\n    public static async Task Function_Reverses_Numbers()\n    {\n        // Arrange\n        using var shutdownAfter = new CancellationTokenSource(TimeSpan.FromSeconds(1));\n\n        using var timeout = CancellationTokenSource.CreateLinkedTokenSource(\n            shutdownAfter.Token,\n            TestContext.Current.CancellationToken);\n\n        using var server = new LambdaTestServer()\n        {\n            OnInvocationCompleted = async (_, _) =\u003e await shutdownAfter.CancelAsync(),\n        };\n\n        await server.StartAsync(timeout.Token);\n\n        int[] value = [1, 2, 3];\n        string json = JsonSerializer.Serialize(value);\n\n        LambdaTestContext context = await server.EnqueueAsync(json);\n\n        using var httpClient = server.CreateClient();\n\n        // Act\n        await ReverseFunction.RunAsync(httpClient, timeout.Token);\n\n        // Assert\n        Assert.True(context.Response.TryRead(out LambdaTestResponse? response));\n        Assert.True(response.IsSuccessful);\n\n        json = await response.ReadAsStringAsync();\n        int[]? actual = JsonSerializer.Deserialize\u003cint[]\u003e(json);\n\n        Assert.NotNull(actual);\n        Assert.Equal([3, 2, 1], actual);\n    }\n}\n```\n\nThe key parts to call out here are:\n\n  1. An instance of `LambdaTestServer` is created and then the `StartAsync()` method called with a `CancellationToken` that allows the test to stop the function. In the example here the token is signalled with a timeout, but you could also write code to stop the processing based on arbitrary criteria.\n  1. The request that the Lambda function should be invoked with is passed to `EnqueueAsync()`. This can be specified with an instance of `LambdaTestRequest` for fine-grained control, but there are overloads that accept `byte[]` and `string`. You could also make your own extensions to serialize objects to JSON using the serializer of your choice.\n  1. `EnqueueAsync()` returns a `LambdaTestContext`. This contains a reference to the `LambdaTestRequest` and a `ChannelReader\u003cLambdaTestResponse\u003e`. This channel reader can be used to await the request being processed by the function under test.\n  1. Once the request is enqueued, an `HttpClient` is obtained from the test server and passed to the function to test with the cancellation token and run by calling `RunAsync()`.\n  1. Once the function processing completes after the `CancellationToken` is signalled, the channel reader is read to obtain the `LambdaTestResponse` for the request that was enqueued.\n  1. Once this is returned from the channel reader, the response is checked for success using `IsSuccessful` and then the `Content` (which is a `byte[]`) is deserialized into the expected response to be asserted on. Again, you could make your own extensions to deserialize the response content into `string` or objects from JSON.\n\nThe library itself targets `net8.0` and `net9.0` so requires your test project to target at least .NET 8.\n\n#### Sequence Diagram\n\nThe sequence diagram below illustrates the flow of events for a test using the test server for the above example.\n\n```mermaid\nsequenceDiagram\n    autonumber\n\n    participant T as Test Method\n    participant S as Lambda Test Server\n    participant F as Lambda Function\n    participant H as Handler\n\n    title How AWS Lambda Test Server Works\n\n    note over T:Arrange\n\n    T-\u003e\u003e+S: Start test server\n\n    S-\u003e\u003eS:Start HTTP server\n\n    S--\u003e\u003eT: \n\n    T-\u003e\u003eS:Queue request\n\n    note over S:Request is queued\n\n    S--\u003e\u003eT:LambdaTestContext\n\n    T-\u003e\u003e+F:Create function with HttpClient for Test Server\n\n    note over T:Act\n\n    note over T:Wait for request(s)\u003cbr/\u003eto be handled\n\n    loop Poll for Lambda invocations\n\n        F-\u003e\u003eS:GET /{LambdaVersion}/runtime/invocation/next\n\n        note over S:Request is dequeued\n\n        S--\u003e\u003eF:HTTP 200\n\n        F-\u003e\u003e+H:Invoke Handler\n\n        note over H:System Under Test\n\n        H--\u003e\u003e-F:Response\n\n        alt Invocation is handled successfully\n\n        F-\u003e\u003eS:POST /{LambdaVersion}/runtime/invocation/{AwsRequestId}/response\n\n        else Invocation throws an exception\n\n        F-\u003e\u003eS:POST /{LambdaVersion}/runtime/invocation/{AwsRequestId}/error\n\n        end\n\n        note over S:Associate response with\u003cbr/\u003eLambdaTestContext\n\n        S-)T:Signal request handled\u003cbr/\u003eon LambdaTestContext\n\n        S--\u003e\u003eF:HTTP 204\n\n        T-\u003e\u003eF:Stop Lambda function\n\n        note over F:Terminate client\u003cbr/\u003elisten loop\n\n        deactivate F\n\n    end\n\n    T-\u003e\u003eS:Stop server\n\n    S-\u003e\u003eS:Stop HTTP server\n\n    S--\u003e\u003e T: \n\n    deactivate S\n\n    note over T:Assert\n```\n\n\u003c!--\nGenerated with https://mermaid.live/.\n--\u003e\n\n### Examples\n\nYou can find examples of how to factor your Lambda function and how to test it:\n\n  1. In the [samples][samples];\n  1. In the [unit tests][tests] for this project;\n  1. How I use the library in the tests for my own [Alexa skill][skill].\n\n### Advanced Usage\n\n#### AWS Mobile SDK with Cognito\n\nIf you use either the `ClientContext` or `Identity` properties on `ILambdaContext` in your function, you can specify the serialized JSON for either property as a `string` when enqueueing a request to the test server to be made available to the function invocation.\n\nAn example of providing these values from an xunit test is shown below:\n\n```csharp\nusing System.Text.Json;\nusing MartinCostello.Testing.AwsLambdaTestServer;\nusing Xunit;\n\nnamespace MyFunctions;\n\npublic static class ReverseFunctionWithMobileSdkTests\n{\n    [Fact]\n    public static async Task Function_Reverses_Numbers_With_Mobile_Sdk()\n    {\n        // Arrange\n        using var shutdownAfter = new CancellationTokenSource(TimeSpan.FromSeconds(1));\n\n        using var timeout = CancellationTokenSource.CreateLinkedTokenSource(\n            shutdownAfter.Token,\n            TestContext.Current.CancellationToken);\n\n        using var server = new LambdaTestServer()\n        {\n            OnInvocationCompleted = async (_, _) =\u003e await shutdownAfter.CancelAsync(),\n        };\n\n        await server.StartAsync(timeout.Token);\n\n        int[] value = [1, 2, 3];\n        string json = JsonSerializer.Serialize(value);\n        byte[] content = Encoding.UTF8.GetBytes(json);\n\n        var request = new LambdaTestRequest(content)\n        {\n            ClientContext = \"\"\"{ \"client\": { \"app_title\": \"my-app\" } }\"\"\",\n            CognitoIdentity = \"\"\"{ \"identityId\": \"my-identity\" }\"\"\",\n        };\n\n        LambdaTestContext context = await server.EnqueueAsync(json);\n\n        using var httpClient = server.CreateClient();\n\n        // Act\n        await ReverseFunction.RunAsync(httpClient, timeout.Token);\n\n        // Assert\n        Assert.True(context.Response.TryRead(out LambdaTestResponse? response));\n        Assert.True(response.IsSuccessful);\n\n        json = await response.ReadAsStringAsync();\n        int[]? actual = JsonSerializer.Deserialize\u003cint[]\u003e(json);\n\n        Assert.NotNull(actual);\n        Assert.Equal([3, 2, 1], actual);\n    }\n}\n```\n\n#### Lambda Runtime Options\n\nIf your function makes use of the various other properties in the `ILambdaContext` passed to the function, you can pass an instance of `LambdaTestServerOptions` to the constructor of `LambdaTestServer` to change the values the server provides to `LambdaBootstrap` before it invokes your function.\n\nOptions you can specify include the function memory size, timeout and ARN.\n\n\u003e The test server does not enforce these values at runtime, unlike the production AWS Lambda environment. They are provided for you to drive the usage of such properties in the code you are testing and should not be relied on to ensure that your function does not take too long to execute or uses too much memory during execution or any other constraints, as appropriate.\n\nAn example of this customisation for an xunit test is shown below:\n\n```csharp\nusing System.Text.Json;\nusing MartinCostello.Testing.AwsLambdaTestServer;\nusing Xunit;\n\nnamespace MyFunctions;\n\npublic static class ReverseFunctionWithCustomOptionsTests\n{\n    [Fact]\n    public static async Task Function_Reverses_Numbers_With_Custom_Options()\n    {\n        // Arrange\n        using var shutdownAfter = new CancellationTokenSource(TimeSpan.FromSeconds(1));\n\n        using var timeout = CancellationTokenSource.CreateLinkedTokenSource(\n            shutdownAfter.Token,\n            TestContext.Current.CancellationToken);\n\n        var options = new LambdaTestServerOptions()\n        {\n            FunctionMemorySize = 256,\n            FunctionTimeout = TimeSpan.FromSeconds(30),\n            FunctionVersion = 42,\n        };\n\n        using var server = new LambdaTestServer(options)\n        {\n            OnInvocationCompleted = async (_, _) =\u003e await shutdownAfter.CancelAsync(),\n        };\n\n        await server.StartAsync(timeout.Token);\n\n        int[] value = [1, 2, 3];\n        string json = JsonSerializer.Serialize(value);\n\n        LambdaTestContext context = await server.EnqueueAsync(json);\n\n        using var httpClient = server.CreateClient();\n\n        // Act\n        await ReverseFunction.RunAsync(httpClient, timeout.Token);\n\n        // Assert\n        Assert.True(context.Response.TryRead(out LambdaTestResponse? response));\n        Assert.True(response.IsSuccessful);\n\n        json = await response.ReadAsStringAsync();\n        int[]? actual = JsonSerializer.Deserialize\u003cint[]\u003e(json);\n\n        Assert.NotNull(actual);\n        Assert.Equal([3, 2, 1], actual);\n    }\n}\n```\n\n#### Logging from the Test Server\n\nTo help diagnose failing tests, the `LambdaTestServer` outputs logs of the requests it receives to the emulated AWS Lambda Runtime it provides. To route the logging output to a location of your choosing, you can use the configuration callbacks, such as the constructor overload that accepts an `Action\u003cIServiceCollection\u003e` or the `Configure` property on the `LambdaTestServerOptions` class.\n\nHere's an example of configuring the test server to route its logs to xunit using the [xunit-logging][xunit-logging] library:\n\n```csharp\nusing System.Text.Json;\nusing MartinCostello.Logging.XUnit;\nusing MartinCostello.Testing.AwsLambdaTestServer;\nusing Microsoft.Extensions.DependencyInjection;\nusing Microsoft.Extensions.Logging;\nusing Xunit;\nusing Xunit.Abstractions;\n\nnamespace MartinCostello.Testing.AwsLambdaTestServer;\n\npublic class ReverseFunctionWithLoggingTests(ITestOutputHelper outputHelper) : ITestOutputHelperAccessor\n{\n    public ITestOutputHelper? OutputHelper { get; set; } = outputHelper;\n\n    [Fact]\n    public async Task Function_Reverses_Numbers_With_Logging()\n    {\n        // Arrange\n        using var shutdownAfter = new CancellationTokenSource(TimeSpan.FromSeconds(1));\n\n        using var timeout = CancellationTokenSource.CreateLinkedTokenSource(\n            shutdownAfter.Token,\n            TestContext.Current.CancellationToken);\n\n        using var server = new LambdaTestServer(\n            (services) =\u003e services.AddLogging(\n                (builder) =\u003e builder.AddXUnit(this)))\n        {\n            OnInvocationCompleted = async (_, _) =\u003e await shutdownAfter.CancelAsync(),\n        };\n\n        await server.StartAsync(timeout.Token);\n\n        int[] value = [1, 2, 3];\n        string json = JsonSerializer.Serialize(value);\n\n        LambdaTestContext context = await server.EnqueueAsync(json);\n\n        using var httpClient = server.CreateClient();\n\n        // Act\n        await ReverseFunction.RunAsync(httpClient, timeout.Token);\n\n        // Assert\n        Assert.True(context.Response.TryRead(out LambdaTestResponse? response));\n        Assert.True(response.IsSuccessful);\n\n        json = await response.ReadAsStringAsync();\n        int[]? actual = JsonSerializer.Deserialize\u003cint[]\u003e(json);\n\n        Assert.NotNull(actual);\n        Assert.Equal([3, 2, 1], actual);\n    }\n}\n```\n\nThis then outputs logs similar to the below into the xunit test results:\n\n```sh\nTest Name:     Function_Reverses_Numbers_With_Logging\nTest Outcome:  Passed\nResult StandardOutput:\n[2025-08-13 14:02:42Z] info: Microsoft.AspNetCore.Hosting.Diagnostics[1]\n      Request starting HTTP/1.1 GET http://localhost/2018-06-01/runtime/invocation/next - - -\n[2025-08-13 14:02:42Z] info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]\n      Executing endpoint 'HTTP: GET /{LambdaVersion}/runtime/invocation/next'\n[2025-08-13 14:02:42Z] info: MartinCostello.Testing.AwsLambdaTestServer.RuntimeHandler[0]\n      Waiting for new request for Lambda function with ARN arn:aws:lambda:eu-west-1:123456789012:function:test-function.\n[2025-08-13 14:02:42Z] info: MartinCostello.Testing.AwsLambdaTestServer.RuntimeHandler[0]\n      Invoking Lambda function with ARN arn:aws:lambda:eu-west-1:123456789012:function:test-function for request Id 0bd92f10-4b53-4fa4-a522-0728d378069d and trace Id Root=1-689c9b02-c20648da88f4e3dc13b4a3dc.\n[2025-08-13 14:02:42Z] info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]\n      Executed endpoint 'HTTP: GET /{LambdaVersion}/runtime/invocation/next'\n[2025-08-13 14:02:42Z] info: Microsoft.AspNetCore.Hosting.Diagnostics[2]\n      Request finished HTTP/1.1 GET http://localhost/2018-06-01/runtime/invocation/next - 200 - application/json 22.6349ms\n[2025-08-13 14:02:42Z] info: Microsoft.AspNetCore.Hosting.Diagnostics[1]\n      Request starting HTTP/1.1 POST http://localhost/2018-06-01/runtime/invocation/0bd92f10-4b53-4fa4-a522-0728d378069d/response - application/json 7\n[2025-08-13 14:02:42Z] info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]\n      Executing endpoint 'HTTP: POST /{LambdaVersion}/runtime/invocation/{AwsRequestId}/response'\n[2025-08-13 14:02:42Z] info: MartinCostello.Testing.AwsLambdaTestServer.RuntimeHandler[0]\n      Invoked Lambda function with ARN arn:aws:lambda:eu-west-1:123456789012:function:test-function for request Id 0bd92f10-4b53-4fa4-a522-0728d378069d: [3,2,1].\n[2025-08-13 14:02:42Z] info: MartinCostello.Testing.AwsLambdaTestServer.RuntimeHandler[0]\n      Completed processing AWS request Id 0bd92f10-4b53-4fa4-a522-0728d378069d for Lambda function with ARN arn:aws:lambda:eu-west-1:123456789012:function:test-function in 52 milliseconds.\n[2025-08-13 14:02:42Z] info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]\n      Executed endpoint 'HTTP: POST /{LambdaVersion}/runtime/invocation/{AwsRequestId}/response'\n[2025-08-13 14:02:42Z] info: Microsoft.AspNetCore.Hosting.Diagnostics[2]\n      Request finished HTTP/1.1 POST http://localhost/2018-06-01/runtime/invocation/0bd92f10-4b53-4fa4-a522-0728d378069d/response - 204 - - 9.3711ms\n```\n\n#### Custom Lambda Server\n\nIt is also possible to use `LambdaTestServer` with a custom [`IServer`][iserver] implementation by overriding the [`ConfigureWebHost()`][configure-webhost] method in a derived class.\n\nThis can be used, for example, to host the Lambda test server in a real HTTP server with [Kestrel][kestrel] that can be accessed remotely instead of being hosted in-memory with the [`TestServer`][testserver] class.\n\nFor examples of this use case, see the `MinimalApi` example project and its test project in the [samples][samples].\n\n## Feedback\n\nAny feedback or issues can be added to the issues for this project in [GitHub][issues].\n\n## Repository\n\nThe repository is hosted in [GitHub][repo]: \u003chttps://github.com/martincostello/lambda-test-server.git\u003e\n\n## License\n\nThis project is licensed under the [Apache 2.0][license] license.\n\n## Building and Testing\n\nCompiling the library yourself requires Git and the [.NET SDK][dotnet-sdk] to be installed.\n\nTo build and test the library locally from a terminal/command-line, run one of the following set of commands:\n\n```powershell\ngit clone https://github.com/martincostello/lambda-test-server.git\ncd lambda-test-server\n./build.ps1\n```\n\n[build-badge]: https://github.com/martincostello/lambda-test-server/actions/workflows/build.yml/badge.svg?branch=main\u0026event=push\n[build-status]: https://github.com/martincostello/lambda-test-server/actions/workflows/build.yml?query=branch%3Amain+event%3Apush \"Continuous Integration for this project\"\n[configure-webhost]: https://github.com/martincostello/lambda-test-server/blob/4e78b4077c4b20dcd5b6976c115a4c5bdfeba7d4/src/AwsLambdaTestServer/LambdaTestServer.cs#L308-L323 \"LambdaTestServer.ConfigureWebHost() method\"\n[coverage-badge]: https://codecov.io/gh/martincostello/lambda-test-server/branch/main/graph/badge.svg\n[coverage-report]: https://codecov.io/gh/martincostello/lambda-test-server \"Code coverage report for this project\"\n[custom-lambda-runtime]: https://aws.amazon.com/blogs/developer/net-core-3-0-on-lambda-with-aws-lambdas-custom-runtime/ \".NET Core 3.0 on Lambda with AWS Lambda's Custom Runtime on the AWS Developer Blog\"\n[dotnet-sdk]: https://dotnet.microsoft.com/download \"Download the .NET SDK\"\n[iserver]: https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.hosting.server.iserver \"IServer Interface on docs.microsoft.com\"\n[issues]: https://github.com/martincostello/lambda-test-server/issues \"Issues for this project on GitHub.com\"\n[kestrel]: https://learn.microsoft.com/aspnet/core/fundamentals/servers/kestrel \"Kestrel web server in ASP.NET Core\"\n[lambda-runtime-support]: https://www.nuget.org/packages/Amazon.Lambda.RuntimeSupport/ \"Download Amazon.Lambda.RuntimeSupport from NuGet\"\n[license]: https://www.apache.org/licenses/LICENSE-2.0.txt \"The Apache 2.0 license\"\n[package-badge-downloads]: https://img.shields.io/nuget/dt/MartinCostello.Testing.AwsLambdaTestServer?logo=nuget\u0026label=Downloads\u0026color=blue\n[package-badge-version]: https://img.shields.io/nuget/v/MartinCostello.Testing.AwsLambdaTestServer?logo=nuget\u0026label=Latest\u0026color=blue\n[package-download]: https://www.nuget.org/packages/MartinCostello.Testing.AwsLambdaTestServer \"Download MartinCostello.Testing.AwsLambdaTestServer from NuGet\"\n[repo]: https://github.com/martincostello/lambda-test-server \"This project on GitHub.com\"\n[samples]: https://github.com/martincostello/lambda-test-server/tree/main/samples \"Sample functions and tests\"\n[scorecard-badge]: https://api.securityscorecards.dev/projects/github.com/martincostello/lambda-test-server/badge\n[scorecard-report]: https://securityscorecards.dev/viewer/?uri=github.com/martincostello/lambda-test-server \"OpenSSF Scorecard for this project\"\n[skill]: https://github.com/martincostello/alexa-london-travel/blob/e50ca325101d5b57a64feaffe0a868e573dc2e40/test/LondonTravel.Skill.Tests/EndToEndTests.cs#L208-L252 \"Alexa London Travel's end-to-end tests\"\n[testhost]: https://www.nuget.org/packages/Microsoft.AspNetCore.TestHost \"Download Microsoft.AspNetCore.TestHost from NuGet\"\n[tests]: https://github.com/martincostello/lambda-test-server/blob/main/tests/AwsLambdaTestServer.Tests/Examples.cs \"Unit test examples\"\n[testserver]: https://learn.microsoft.com/dotnet/api/microsoft.aspnetcore.testhost.testserver \"TestServer Class on docs.microsoft.com\"\n[xunit-logging]: https://www.nuget.org/packages/MartinCostello.Logging.XUnit \"Download MartinCostello.Logging.XUnit from NuGet\"\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmartincostello%2Flambda-test-server","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmartincostello%2Flambda-test-server","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmartincostello%2Flambda-test-server/lists"}