{"id":37051385,"url":"https://github.com/cympatic/stub","last_synced_at":"2026-01-14T05:56:14.616Z","repository":{"id":45346334,"uuid":"382679905","full_name":"cympatic/stub","owner":"cympatic","description":"Isolated testing of system components through contract testing","archived":false,"fork":false,"pushed_at":"2024-09-18T16:10:37.000Z","size":275,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-11-23T06:24:00.967Z","etag":null,"topics":["bdd","dotnet","microservice","microsoft","specflow","testing"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cympatic.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-07-03T18:00:06.000Z","updated_at":"2024-09-18T16:10:38.000Z","dependencies_parsed_at":"2024-08-22T15:15:59.776Z","dependency_job_id":null,"html_url":"https://github.com/cympatic/stub","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/cympatic/stub","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cympatic%2Fstub","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cympatic%2Fstub/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cympatic%2Fstub/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cympatic%2Fstub/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cympatic","download_url":"https://codeload.github.com/cympatic/stub/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cympatic%2Fstub/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28412099,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T05:26:33.345Z","status":"ssl_error","status_checked_at":"2026-01-14T05:21:57.251Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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":["bdd","dotnet","microservice","microsoft","specflow","testing"],"created_at":"2026-01-14T05:56:14.120Z","updated_at":"2026-01-14T05:56:14.609Z","avatar_url":"https://github.com/cympatic.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Continuous integration build](https://github.com/cympatic/stub/actions/workflows/ci.yml/badge.svg)](https://github.com/cympatic/stub)\n[![NuGet](https://img.shields.io/nuget/v/Cympatic.Extensions.Stub)](https://www.nuget.org/packages/Cympatic.Extensions.Stub)\n# Isolated testing of system components through contract testing\n\nA C# .NET based, lightweight stub server that mimics the functionality of an external service, commonly used by microservices. \n\n## Key Features\n\n- Test locally system and integration tests of system components\n- Reduce the dependency on complex and/or expensive test environments\n- Per-request conditional responses\n- Recording requests\n- Easy to use\n\nBy using contract testing[^1^] in integration tests for projects with dependencies on external services, the stub server can provide configurable responses for requests made to these services. Each request is recorded and can be validated as part of these integration tests.\n[^1^]: [Consumer-driven Contract Testing (CDC)](https://microsoft.github.io/code-with-engineering-playbook/automated-testing/cdc-testing/)\n\n\u003e [!NOTE]\n\u003e In discussions of integration tests, the tested project is frequently called \"SUT\", the System Under Test in short. \n\nThe stub server creates a in-memory web host for the external service to handle the requests and responses for the external service made by the SUT. Creating the stub server can be done within a custom `WebApplicationFactory` [^2^] that might be available in the testproject for integration testing the SUT. An example of a custom [`WebApplicationFactory`](source/Examples/Cympatic.Stub.Example.WebApplication.IntegrationTests/Factories/ExampleWebApplicationFactory.cs) can be found in the [example testproject](source/Examples/Cympatic.Stub.Example.WebApplication.IntegrationTests).\n\n[^2^]: [Integration tests in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests)\n\n# Usage\n\n## Setup `StubServer` in a custom [WebApplicationFactory](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.testing.webapplicationfactory-1)\nAdd the initialization of the stub server in the constructor of your custom `WebApplicationFactory`.\n``` C#\n_stubServer = new StubServer();\n```\n\nAdd proxy methodes for adding responses to the `StubServer`.\n``` c#\npublic Task\u003cResponseSetup\u003e AddResponseSetupAsync(ResponseSetup responseSetup, CancellationToken cancellationToken = default)\n    =\u003e _stubServer.AddResponseSetupAsync(responseSetup, cancellationToken);\n\npublic Task AddResponsesSetupAsync(IEnumerable\u003cResponseSetup\u003e responseSetups, CancellationToken cancellationToken = default)\n    =\u003e _stubServer.AddResponsesSetupAsync(responseSetups, cancellationToken);\n```\n\nAdd proxy methode for reading requests from the `StubServer`.\n``` c#\npublic Task\u003cIEnumerable\u003cReceivedRequest\u003e\u003e FindReceivedRequestsAsync(ReceivedRequestSearchParams searchParams, CancellationToken cancellationToken = default)\n    =\u003e _stubServer.FindReceivedRequestsAsync(searchParams, cancellationToken);\n```\n\nAdd proxy methodes for removing responses and received requests from the `StubServer`.\n``` c#\npublic Task ClearResponsesSetupAsync(CancellationToken cancellationToken = default)\n    =\u003e _stubServer.ClearResponsesSetupAsync(cancellationToken);\n\npublic Task ClearReceivedRequestsAsync(CancellationToken cancellationToken = default)\n    =\u003e _stubServer.ClearReceivedRequestsAsync(cancellationToken);\n```\n\nOverride the `Dispose` since the `StubServer` is a disposable object.\n``` C#\nprotected override void Dispose(bool disposing)\n{\n    base.Dispose(disposing);\n\n    if (disposing)\n    {\n        _stubServer.Dispose();\n    }\n}\n```\n\nOverride the `CreateHost` of the [WebApplicationFactory](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.testing.webapplicationfactory-1) to configure the base address of the used external service.\n``` C#\nprotected override void ConfigureWebHost(IWebHostBuilder builder)\n{\n    builder.ConfigureServices((context, services) =\u003e\n    {\n        context.Configuration[\"ExternalApi\"] = _stubServer.BaseAddressStub.ToString();\n    });\n\n    base.ConfigureWebHost(builder);\n}\n```\n\n## Use in unit test\n\nCreate a test class that implements a [IClassFixture\u003c\u003e](https://xunit.net/docs/shared-context#class-fixture) interface referencing the custom `WebApplicationFactory` to share object instances across the tests in the class.\n``` C#\npublic class WeatherForecastTests : IClassFixture\u003cExampleWebApplicationFactory\u003cProgram\u003e\u003e\n```\n\nIn the constructor of the test class use the factory to create the `HttpClient` and clear the `ResponseSetup` and `ReceivedRequest` data from the `StubServer`\n``` C#\npublic WeatherForecastTests(ExampleWebApplicationFactory\u003cProgram\u003e factory)\n{\n    _factory = factory;\n    _httpClient = _factory.CreateClient();\n\n    _factory.ClearResponsesSetupAsync();\n    _factory.ClearReceivedRequestsAsync();\n}\n```\n\nA typical test uses the factory to setup the response and process the request through the `HttpClient`. The request to the external service can be validated.\n``` C#\n[Fact]\npublic async Task GetAllWeatherForecasts()\n{\n    static IEnumerable\u003cWeatherForecast\u003e GetItems()\n    {\n        for (var i = 0; i \u003c NumberOfItems; i++)\n        {\n            yield return GenerateWeatherForecast(i);\n        }\n    }\n\n    // Arrange\n    var expected = GetItems().ToList();\n    var responseSetup = new ResponseSetup\n    {\n        Path = \"/external/api/weatherforecast\",\n        HttpMethods = [HttpMethod.Get.ToString()],\n        ReturnStatusCode = HttpStatusCode.OK,\n        Response = expected\n    };\n    await _factory.AddResponseSetupAsync(responseSetup);\n\n    var expectedReceivedRequests = new List\u003cReceivedRequest\u003e\n    {\n        new(responseSetup.Path, responseSetup.HttpMethods[0], responseSetup.Query, responseSetup.Headers, string.Empty, true)\n    };\n\n    // Act\n    var response = await _httpClient.GetAsync(\"/weatherforecast\");\n\n    // Assert\n    var actual = await response.Content.ReadFromJsonAsync\u003cIEnumerable\u003cWeatherForecast\u003e\u003e();\n    actual.Should().BeEquivalentTo(expected);\n\n    var actualReceivedRequests = await _factory.FindReceivedRequestsAsync(new ReceivedRequestSearchParams(\"/external/api/weatherforecast\", [HttpMethod.Get.ToString()]));\n    actualReceivedRequests.Should().BeEquivalentTo(expectedReceivedRequests, options =\u003e options\n        .Excluding(_ =\u003e _.Headers)\n        .Excluding(_ =\u003e _.Id)\n        .Excluding(_ =\u003e _.CreatedDateTime));\n}\n```\n- Prepare the `ResponseSetup`:\n    - Set `Path` and `HttpMethods` to a partial path and HttpMethod of the expected request used by the external service.\n    - Set `ReturnStatusCode` to the desired [HttpStatusCode](https://learn.microsoft.com/en-us/dotnet/api/system.net.httpstatuscode).\n    - Set `Response` to the desired reponse of the external service.\n- Add the `ResponseSetup` to the `StubServer` with the method `AddResponseSetupAsync`\n\n\u003e [!TIP]\n\u003e Multiple `ResponseSetup` can be added in 1 call with the method `AddResponsesSetupAsync`!\n\n- Process the request to the SUT using the `HttpClient`\n- Verify the response from the SUT\n- Verify the request made to the external service\n    - Use the method `FindReceivedRequestsAsync` to locate the request made to the external service. The request can be found on a combination of `Path`, `HttpMethod`, and `Query`.\n\n\u003e [!IMPORTANT]\n\u003e `ReceivedRequest` can only be found when there is a matching `ResponseSetup` \n\n---\n\n## Release notes\n\n1.1.0\n- Removed obsolete methods in `StubServer`:\n  - `CreateApiService\u003cTApiService\u003e`\n  - `CreateApiService(type type\u003e)`\n- Moved the folder `Services` to the folder `Internal`, including classes\n- Updated access modifier for classes in the folder `Services` from `public` to `internal`. The following classes are impacted:\n  - `Results/ApiServiceResult`\n  - `Results/ApiServiceResult\u003cTModel\u003e`\n  - `ApiService`\n  - `ReceivedRequestApiService`\n  - `SetupResponseApiService`\n- Change access modifier for class `UriExtensions` from `public` to `internal` and moved the class to the folder `Internal`\n- Removed interface `IApiResult`\n\n1.0.3\n- Change the default for the usage of the development certificate in the `StubServer` from `true` to `false`\n- Changed constructor of `StubServer` so that users can chose a certificate used by the `StubServer` and how to handle this certificate in the `HttpClient`\n- Add methods for add and remove `ResponseSetup` items to the `StubServer`\n- Add methods for find and remove `ReceivedRequest` to the `StubServer`\n- [Deprecated] methods in `StubServer`:\n  - `CreateApiService\u003cTApiService\u003e`\n  - `CreateApiService(type type\u003e)`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcympatic%2Fstub","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcympatic%2Fstub","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcympatic%2Fstub/lists"}