Using WireMock in Integration Tests for ASP.NET Core APIs

Overview

If you have a reasonably-complex API project in ASP.NET, chances are pretty good that it needs to call other APIs for some operations. Having integration tests to accommodate different behavior for those external API calls - without having to call the real version of the external API can be super helpful. Turns out this is easy to do using the handy WireMock.net NuGet package.

Show me the Code!

The code I used as a reference for this article is in the same repo as the previous posts I did about integration testing, and the external API call and the WireMock feature has been added in the 4-api-with-postgres-and-auth repo.

Application Code Calls an External API

I updated the simple little API to make an external API call using a typed HTTP client:

 1public class ProductsController(LocalContext context, ProductValidator validator, IExternalApiClient apiClient) : ControllerBase
 2{
 3    [HttpGet]
 4    public async Task<IEnumerable<Product>> GetProducts(string category = "all")
 5    {
 6        var sampleClaims = await apiClient.GetSampleResult(HttpContext);
 7
 8        return await context.Products
 9            .Where(p => p.Category == category || category == "all")
10            .ToListAsync();
11    }
12    //...
13}

The typed HTTP client code uses a demo/test API method available on the demo Duende IdentityServer that would use a bearer token for authentication and return a list of claims. The entire code for the client is below:

 1public record InternalClaim(string Type, string Value);
 2
 3public interface IExternalApiClient
 4{
 5    Task<List<InternalClaim>> GetSampleResult(HttpContext ctx);
 6}
 7
 8public class ExternalApiClient : IExternalApiClient
 9{
10    private HttpClient Client { get; }
11
12    public ExternalApiClient(HttpClient client, IConfiguration config)
13    {
14        client.BaseAddress = new Uri(config.GetValue<string>("ExternalApiBaseUrl")!);
15        Client = client;
16    }
17
18    public async Task<List<InternalClaim>> GetSampleResult(HttpContext ctx)
19    {
20        var token = await ctx.GetTokenAsync("access_token");
21        Client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
22        var claims = await Client.GetFromJsonAsync<List<InternalClaim>>("api/test");
23        return claims!;
24    }
25}

Note that the ExternalApiBaseUrl is read from configuration (line 14) -- meaning appsettings.json in this case.

If you debug the API and use the .http file to execute the GET v1/products route, you can see the claims returned from the API method.

But we want to make sure this works properly in our integration tests - that's the real point of this article. So read on!

Using WireMock

In its simplest use case, the WireMock.Net library creates a simple http server that listens for requests that you specify and sends responses that you also specify.

If you have any kind of IClassFixture in your integration test project, you are already well-prepped to be able to easily use WireMock in your integration tests.

In the sample project I've been working with, that class is DatabaseFixture.

First, I added a NuGet package reference to WireMock.Net.

Then I created a public string property which will have the base address of the WireMock server:

1public string ExternalApiBaseUrlOverride { get; private set; } = null!;

Then in the InitializeAsync method I added this code:

 1var server = WireMockServer.Start();
 2
 3var claims = new List<InternalClaim>
 4{
 5    new("email", "hi@there.com"),
 6    new("role", "admin"),
 7    new("sub", "1234567890")
 8};
 9
10ExternalApiBaseUrlOverride = server.Url!;
11server
12    .Given(
13        Request.Create().WithPath("/api/test").UsingGet()
14    )
15    .RespondWith(
16        Response.Create()
17            .WithStatusCode(200)
18            .WithHeader("Content-Type", "application/json")
19            .WithBody(JsonSerializer.Serialize(claims, new JsonSerializerOptions(JsonSerializerDefaults.Web)))
20    );

The above code sets up a WireMock server, then creates a list of claims that I want to serialize as the response.

It sets the ExternalApiBaseUrlOverride property to the dynamic URL that was generated when the WireMock server started (line 10).

The next big block of server.Given(...).RespondWith(...) is where we say that the GET api/test route should respond with the serialized payload of those hard-coded claims.

Override the appsettings.json Configuration

A final change needed to actually put this WireMock server in play for the tests is to make sure the ExternalApiBaseUrl defined in appsettings.json for the API gets overriden with the value from our DatabaseFixture class.

This is a very simple addition to the CustomApiFactory.ConfigureWebHost method:

1builder.ConfigureAppConfiguration((_, configBuilder) =>
2{
3    configBuilder.AddInMemoryCollection(new Dictionary<string, string>
4    {
5        ["ExternalApiBaseUrl"] = dbFixture.ExternalApiBaseUrlOverride
6    }!);
7});

The above code will do exactly what we want - note the use of the ExternalApiBaseUrlOverride property from the DatabaseFixture class.

Run / debug the tests and they should work fine!

There are many additional features and options for the WireMock library, including:

  • Delays in responses (e.g. simulate an API taking 5 seconds to respond)
  • Fault or error responses for some percentage of the time
  • Regular expression support for complex route / request mapping
  • "Proxy mode" to record actual requests and responses for uses in tests or other scenarios later
  • Dynamic configuration
  • More!

Check the WireMock.Net Wiki for more information.

Keep testing!