Running the API smarter
The problem
Even though the approach that we've seen until now works well enough and it does actually properly function as an integration testing approach, it suffers from a few problems.
- The API needs to be running at all times so the tests can target it
- A database needs to be running at all times so the API that is being called by the tests can target it
Let's start from the first problem
Running the API
Historically, one of the biggest pains when it comes to integration testing is to actually run the API just for the tests and tear it down. Technically we can still implement it for our usecase but we really don't need to.
Microsoft has noticed that this was a problem that many people had for a very long time and they created the WebApplicationFactory
.
The WebApplicationFactory
The WebApplicationFactory
is a class that allows us to create a TestServer
that runs our API.
This API is only accessible in-memory so a regular HttpClient or a browser can't access it.
The only way to access it is to use an HttpClient
specifically created to call this in-memory application.
This make is an excellent candidate for integration testing and it is by far the best way to write them.
Let's see how we can use it for our own existing test.
Using the WebApplicationFactory
First let's shut down the running API. We don't need it anymore.
To use the WebApplicationFactory
we need a few project-level changes.
- In the
csproj
of the test project we need to change theProject Sdk
fromMicrosoft.NET.Sdk
toMicrosoft.NET.Sdk.Web
- We need to add the
Microsoft.AspNetCore.Mvc.Testing
Nuget package
New we can simply add the WebApplicationFactory
as a field in our test class:
private readonly WebApplicationFactory<> _waf = new();
As you might have noticed the WebApplicationFactory
need a generic parameter. This parameter is an assembly scanning marker.
It can be any type inside the project we want to run. We could expose the Program.cs
as an internal to the integration tests project but the preferred approach is to create an assembly marker.
An assembly marker is a simple empty type (usually an interface) that can be used by multiple things for assembly scanning.
public interface IApiMarker {}
Now our WebApplicationFactory field can use this marker and it looks like this:
private readonly WebApplicationFactory<IApiMarker> _waf = new();
Now we can replace the existing HttpClient
initialization code to use an HttpClient
generated from the CreateClient
call of the WebApplicationFactory.
private readonly WebApplicationFactory<IApiMarker> _waf = new();
private readonly HttpClient _client;
private readonly List<Guid> _idsToDelete = new();
public CustomerControllerTests()
{
_client = _waf.CreateClient(new WebApplicationFactoryClientOptions
{
BaseAddress = new Uri("https://localhost:5001")
});
}
Note: We can even pass custom WebApplicationFactoryClientOptions
for our client. This is optional but we are using it just to see how it would look like.
Now we can run the exact same test code and our test will pass:
This is still a completely valid integration test which calls the database and cleans the data up in the end.