Cympatic.Extensions.Stub 1.0.1

There is a newer version of this package available.
See the version list below for details.
dotnet add package Cympatic.Extensions.Stub --version 1.0.1
                    
NuGet\Install-Package Cympatic.Extensions.Stub -Version 1.0.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Cympatic.Extensions.Stub" Version="1.0.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Cympatic.Extensions.Stub" Version="1.0.1" />
                    
Directory.Packages.props
<PackageReference Include="Cympatic.Extensions.Stub" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Cympatic.Extensions.Stub --version 1.0.1
                    
#r "nuget: Cympatic.Extensions.Stub, 1.0.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Cympatic.Extensions.Stub@1.0.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Cympatic.Extensions.Stub&version=1.0.1
                    
Install as a Cake Addin
#tool nuget:?package=Cympatic.Extensions.Stub&version=1.0.1
                    
Install as a Cake Tool

Continuous integration build NuGet

Isolated testing of system components through contract testing

A C# .NET based, lightweight stub server that mimics the functionality of an external service, commonly used by microservices.

Key Features

  • Test locally system and integration tests of system components
  • Reduce the dependency on complex and/or expensive test environments
  • Per-request conditional responses
  • Recording requests
  • Easy to use

By 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. [^1^]: Consumer-driven Contract Testing (CDC)

In discussions of integration tests, the tested project is frequently called "SUT", the System Under Test in short.

The stub server creates a 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 can be found in the example testproject.

[^2^]: Integration tests in ASP.NET Core

Usage

Setup StubServer in a custom WebApplicationFactory

Add the initialization of the stub server in the constructor of your custom WebApplicationFactory and create the API services for configuring responses and retrieving received requests.

_stubServer = new StubServer();
_setupResponseApiService = _stubServer.CreateApiService<SetupResponseApiService>();
_receivedRequestApiService = _stubServer.CreateApiService<ReceivedRequestApiService>();

Add proxy methodes for adding responses to the StubServer.

public Task<ResponseSetup> AddResponseSetupAsync(ResponseSetup responseSetup, CancellationToken cancellationToken = default)
    => _setupResponseApiService.AddAsync(responseSetup, cancellationToken);

public Task AddResponsesSetupAsync(IEnumerable<ResponseSetup> responseSetups, CancellationToken cancellationToken = default)
    => _setupResponseApiService.AddAsync(responseSetups, cancellationToken);

Add proxy methode for reading requests from the StubServer.

public Task<IEnumerable<ReceivedRequest>> FindReceivedRequestsAsync(ReceivedRequestSearchParams searchParams, CancellationToken cancellationToken = default)
    => _receivedRequestApiService.FindAsync(searchParams, cancellationToken);

Add proxy methodes for removing responses and received requests from the StubServer.

public Task ClearResponsesSetupAsync(CancellationToken cancellationToken = default)
    => _setupResponseApiService.RemoveAllAsync(cancellationToken);

public Task ClearReceivedRequestsAsync(CancellationToken cancellationToken = default)
    => _receivedRequestApiService.RemoveAllAsync(cancellationToken);

Override the Dispose since the StubServer is a disposable object.

protected override void Dispose(bool disposing)
{
    base.Dispose(disposing);

    if (disposing)
    {
        _stubServer.Dispose();
    }
}

Override the CreateHost of the WebApplicationFactory to configure the base address of the used external service.

protected override IHost CreateHost(IHostBuilder builder)
{
    builder.ConfigureServices((context, services) =>
    {
        context.Configuration["ExternalApi"] = _stubServer.BaseAddressStub.ToString();
    });

    return base.CreateHost(builder);
}

Use in unit test

Create a test class that implements a IClassFixture<> interface referencing the custom WebApplicationFactory to share object instances across the tests in the class.

public class WeatherForecastTests : IClassFixture<ExampleWebApplicationFactory<Program>>

In the constructor of the test class use the factory to create the HttpClient and clear the ResponseSetup and ReceivedRequest data from the StubServer

public WeatherForecastTests(ExampleWebApplicationFactory<Program> factory)
{
    _factory = factory;
    _httpClient = _factory.CreateClient();

    _factory.ClearResponsesSetupAsync();
    _factory.ClearReceivedRequestsAsync();
}

A 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.

[Fact]
public async Task GetAllWeatherForecasts()
{
    static IEnumerable<WeatherForecast> GetItems()
    {
        for (var i = 0; i < NumberOfItems; i++)
        {
            yield return GenerateWeatherForecast(i);
        }
    }

    // Arrange
    var expected = GetItems().ToList();
    var responseSetup = new ResponseSetup
    {
        Path = "/external/api/weatherforecast",
        HttpMethods = [HttpMethod.Get.ToString()],
        ReturnStatusCode = HttpStatusCode.OK,
        Response = expected
    };
    await _factory.AddResponseSetupAsync(responseSetup);

    var expectedReceivedRequests = new List<ReceivedRequest>
    {
        new(responseSetup.Path, responseSetup.HttpMethods[0], responseSetup.Query, responseSetup.Headers, string.Empty, true)
    };

    // Act
    var response = await _httpClient.GetAsync("/weatherforecast");

    // Assert
    var actual = await response.Content.ReadFromJsonAsync<IEnumerable<WeatherForecast>>();
    actual.Should().BeEquivalentTo(expected);

    var actualReceivedRequests = await _factory.FindReceivedRequestsAsync(new ReceivedRequestSearchParams("/external/api/weatherforecast", [HttpMethod.Get.ToString()]));
    actualReceivedRequests.Should().BeEquivalentTo(expectedReceivedRequests, options => options
        .Excluding(_ => _.Headers)
        .Excluding(_ => _.Id)
        .Excluding(_ => _.CreatedDateTime));
}
  • Prepare the ResponseSetup:
    • Set Path and HttpMethods to a partial path and HttpMethod of the expected request used by the external service.
    • Set ReturnStatusCode to the desired HttpStatusCode.
    • Set Response to the desired reponse of the external service.
  • Add the ResponseSetup to the StubServer with the method AddResponseSetupAsync

Multiple ResponseSetup can be added in 1 call with the method AddResponsesSetupAsync!

  • Process the request to the SUT using the HttpClient
  • Verify the response from the SUT
  • Verify the request made to the external service
    • 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.

ReceivedRequest can only be found when there is a matching ResponseSetup

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net6.0

    • No dependencies.
  • net7.0

    • No dependencies.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.0 168 9/18/2024
1.0.3 158 8/23/2024
1.0.2 164 8/14/2024
1.0.1 150 8/14/2024
1.0.0 148 8/12/2024