Hello there!
What Testcontainers
is
Testcontainer
is a lightweight library to manage Docker
container and images. It is designed to be used in integration tests to run your third party services (dependencies) in containers and create isolated and reproducible environment each time you want to run your tests. With this library, you can avoid creating mocks or in-memory implementation and test your code against real dependencies.
Use cases
Usually, to use this library, you need to follow several basic steps.
Start a container in the test initialization code or in each test separatly:
Use connectionString
directly or through EF Core
, run you tests, and dispose containers/connections/etc. when you’re done by using await postgres.StopAsync()
. In this case, your dependencies are running in Docker
containers but the service under test is not.
For my application, I decided to test everything in containers, even ASP.NET Core
API because the Docker
container in the primary way to distribute my application. So, by running tests, I want to be sure at least the core subset of features is ok.
So, the first thing you need to do is to build your Docker
image. You can do it manually but of course we’re to lazy to do everything by hand. If we have a tool to run containers, probably we can use it to build them. Here is example from documentation:
And here is the problem, even that technically Testcontainers
supports it, you can build a container on the fly, it doesn’t work with Docker Buildx
and complex multistage dockerfiles. Even thought the syntax of Dockerfile
is pretty much the same. The resulting image depends on a runner especially if it has optional stages or something “not standard”. So, unfortunately, you need to do it manually.
When the image is built, let’s assume it is named app
. You can use the following code to run your service in the Docker container:
And later you can use baseAddress
to configure your HTTP client to call your APIs.
Example of GitHub Actions
The previous code snippen is enough to run tests in container locally but with GitHub Actions
you have another problem, well you need to run them. You can go easy way, install dotnet SDK, install Docker
, build image and run your tests by using dotnet test
. It should work fine.
I decided to go another way and run tests inside Docker
because the main artifact of my application is a Docker
image. I don’t want to build “two versions” of the application. One is a production version as a Docker
image with isolated dependencies. And another one is just a binary for tests, built in a GitHub Runner
. I want to be sure, if tests are passed on the production Docker
image, then the application is ok and the same image (binary, to every last bit) will be delivered to clients. To achieve this, you need to do additional adjustments to your YAML pipeline file and test code.
The first thing is an additional stage in Dockerfile
:
It uses the previous stage (build
) with all built artifacts and dotnet SDK to run tests. It has two parameters --no-restore
and --no-build
to make sure we are using existing artifacts and aren’t trying to build them again.
Then we need to add several steps to the build pipeline:
Important part of this pipeline is “Export image to docker”. By default, the docker/build-push-action
action doesn’t export anything to Docker and with the push
flag sends everything to the registry. But we need the application image to run our tests against. On the next step, we just need to build an image with tests. And then run everything by using Docker CLI
. -v /var/run/docker.sock:/var/run/docker.sock
is required to grant access to Docker
for Testcontainers
.
And update our test initialization code:
It has several updates. The first one is the IMAGE_ID
env variable. It is not required but it is handy if you don’t want to hardcode the image name. And the second one is important. We can’t use localhost
to call our APIs, it won’t work in a container. Instead of using it, we need to check for DOTNET_RUNNING_IN_CONTAINER
and call directly by container’s IP-address.
Add tests output to GitHub Actions
There is a cool library: GitHubActionsTestLogger which allows you to output test results to GitHub Actions summary. But because our tests are running in the container, it won’t be able to do it. To fix it, you need to update your Dockerfile
and tell dotnet to use this logger:
--logger "GitHubActions;summary.includePassedTests=true;summary.includeSkippedTests=true"
And update your YAML pipeline file and add a few parameter to docker run
:
-e GITHUB_SERVER_URL=$GITHUB_SERVER_URL
-e GITHUB_REPOSITORY=$GITHUB_REPOSITORY
-e GITHUB_WORKSPACE=$GITHUB_WORKSPACE
-e GITHUB_SHA=$GITHUB_SHA
-e GITHUB_STEP_SUMMARY=/summary
-v $GITHUB_STEP_SUMMARY:/summary
Conclusion
In this article, we explored how to use Testcontainers
and GitHub Actions
together to run integration tests. It allows us to be confident about our application that all APIs are ok and tests were executed against the production version of an application with real dependencies (no mocks, no in-memory implementations). I demonstrated an example Dockerfile
, a YAML pipeline, and a little bit of code. So, you can try to apply it in your application.