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