Provisioning test infrastructure using Docker and TestContainers
Not 1 system I ever built worked in isolation.
There is always a database, a cache provider, a message bus or some 3rd party service that it is interacting with.
Writing automated tests to ensure quality is a must in my workflow.
Unit tests are great when it comes to fast feedback cycle in the development process.
Integration tests on the other hand will ensure my component integrates neatly into the bigger picture aka System.
Integration tests have an higher degree of set-up complexity since you need to pre-provision the required infrastructure.
Some challenges of this setup are:
Before running tests, you must ensure that the infrastructure is up and running and data is pre-configured in a specific desired state.
Running multiple build pipelines in parallel might result in concurrency or interference between the tests.
This is where Docker and TestContainers come into play.
What are TestContainters?
TestContainers is a library that allows you to write tests using throwaway instances of Docker containers.
Nowadays everything can be pushed into a Docker container that includes the infrastructure we need for our tests.
First thing is to install the default NuGet package by running:
dotnet add package Testcontainers
And here is a small example of setting up an AzureSqlEdge database using a more verbose approach:
This is a great approach if you have some custom-built image that you want to use. However there are fundamentally 2 things wrong here:
It is way too verbose
If you run tests in parallel you will have to manage the port binding and make it dynamic — which is a nightmare sometimes.
Fortunately typing “Testcontainers.” into NuGet will show us plethora of custom built packages for different popular infrastructure components. Thus by just installing Testcontainers.SqlEdge we can transform the verbose setup on top into :
Now we need a way to start our containers when the tests run. I’m running the tests using xUnit, so I can make my test classes inherit from the IAsyncLifetime interface and in it’s methods start/stop the containers.
But it would become cumbersome since you would start/stop containers for each test and also you would have to duplicate all the set-up code across all your tests.
There is an easier way.
Setting up Testcontainers from a WebApplicationFactory
Microsoft provided us with an amazing package Microsoft.AspNetCore.Mvc.Testing
that let’s us set up an in-memory test server that we can use to spin up an application instance for running tests.
WebApplicationFactory<TEntryPoint> is used to create a instance of our API for the integration tests, it will do a few things for us:
Configure and manage the AzureSqlDb container
Reconfigure the API to use the connectionstring to the database running inside the container
Provide us with a HTTP client to call the API
Starting the container instance is done asynchronously using IAsyncLifetime. The container is started inside InitializeAsync before any of the tests run. And it's stopped inside DisposeAsync once the tests have been run.
Putting it all together
Using the WebApplicationFactory together with the xUnit class fixtures we can create a setup that will let us spin up a container at the start of the test suite and then dispose of it once the tests have been run.
Summing it all up
Testcontainers is an awesome solution for pre-provisioning the test infrastructure using Docker. You can pragmatically spin up and tear down your containers for any piece of infrastructure your system needs.
This way your tests run against real components not mocks/stubs which will show you the real image of how things stand.
Running such tests will increase your confidence level in the software that you are delivering and Friday deploys will not scare you that much anymore. (This is not a call to start deploying stuff of Fridays though, especially Friday the 13th’s :P )
If you are interested in seeing how it all works out inside a real test suite I have a full tutorial on Integration Testing in dotnet core right here.