Our own WebApplicationFactory
The WebApplicationFactory has some great functionality out of the box but when we are doing real life testing there are several things that need to be customized. This can be done in each individual test class but a better approach is to create our own custom WebApplicationFactory and customize it in a single place.
Creating the custom WebApplicationFactory
All we need to do to create the custom WebApplicationFactory is to create a class for our new factory and inherit from our WebApplicationFactory.
public class CustomerApiFactory : WebApplicationFactory<IApiMarker>
{
}
Simple as that. Now we have a custom WAF that we can modify in any way we want. For example, it is very common to remove all logging providers from the API we are running for the tests because they don't really need to log anything.
To do that we can override the ConfigureWebHost
method and clear the providers by calling the ConfigureLogging
method.
public class CustomerApiFactory : WebApplicationFactory<IApiMarker>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureLogging(logging =>
{
logging.ClearProviders();
});
}
}
Running one server for all tests
Now to use it in our test class we will change our approach a bit. Up until now we've been creating one server per test. However, we don't need to do so, especially since we are using the same database for all tests. It would be faster to simply create a single server for all tests and run them all sequentially. This might sound like something that will take longer, but with startup and teardown, it is actually significantly faster.
To achieve this, first we need to create a collection fixture class.
This is a simple class that implements the ICollectionFixture
interface and uses the CustomerApiFactory
as the generic type parameter.
public class SharedTestCollection : ICollectionFixture<CustomerApiFactory>
{
}
Now the only thing we need to do is to add a [CollectionDefinition]
attribute and name the collection fixture:
[CollectionDefinition("Shared collection")]
public class SharedTestCollection : ICollectionFixture<CustomerApiFactory>
{
}
To use this shared collection fixture in our test we simply decorate the test class with the [Collection]
attribute,
using the name of the collection definition we want to share.
[Collection("Shared collection")]
public class CustomerControllerTests : IAsyncLifetime
{
...
So now, instead of instantiating in the CustomerApiFactory
itself we will simply inject it through the constructor of the test class.
That way, the lifetime of the CustomerApiFactory
will be a single instance for all our tests, assuming they all have the [Collection]
attribute on them.
This is what the class looks like now:
[Collection("Shared collection")]
public class CustomerControllerTests : IAsyncLifetime
{
private readonly HttpClient _client;
private readonly List<Guid> _idsToDelete = new();
public CustomerControllerTests(CustomerApiFactory customerApiFactory)
{
_client = customerApiFactory.CreateClient(new WebApplicationFactoryClientOptions
{
BaseAddress = new Uri("https://localhost:5001")
});
}
...
This allows us to have full control over the creation and cleanup of both the test collection and each individual test.
Sharing the HttpClient
Now we can make one last change. Instead of creating an HttpClient for all of our tests, we can simply reuse the same one.
To do that, we will create a property in the CustomerApiFactory
for HttpClient
:
public HttpClient HttpClient { get; private set; } = default!;
And then extend IAsyncLifetime
on the CustomerApiFactory
. That way we can implement the InitializeAsync
and DisposeAsync
methods.
We will only use the InitializeAsync
method that will be called ones for all our tests to initialize the HttpClient.
public async Task InitializeAsync()
{
HttpClient = CreateClient(new WebApplicationFactoryClientOptions
{
BaseAddress = new Uri("https://localhost:5001")
});
}
Now in our test class we will simply reference the shared HttpClient:
[Collection("Shared collection")]
public class CustomerControllerTests : IAsyncLifetime
{
private readonly HttpClient _client;
private readonly List<Guid> _idsToDelete = new();
public CustomerControllerTests(CustomerApiFactory customerApiFactory)
{
_client = customerApiFactory.HttpClient;
}
...