fbpx

Isolate your Test Executions with TestContainers

Isolate your Test Executions with TestContainers
Reading Time: 6 minutes

By Andony Núñez, Staff QA Automation Engineer at Growth Acceleration Partners.

What is TestContainers?

TestContainers is an Open Source framework licensed under MIT License to create throwaway, lightweight instances of databases, message brokers, web browsers or just about anything that can run in a Docker container. https://testcontainers.com/

Languages

This framework was initially created for Java, but ports for many other languages such as .NET, Node.js, Python, Go, Ruby and others have been created.
Some features and modules might not be implemented in all languages, but the essential features are available in all of them.

Source: https://testcontainers.com/

Container Runtimes

TestContainers has been developed using Docker Containers by default, but it can be configured to use other container runtimes; see https://java.testcontainers.org/supported_docker_environment/ for more information.

How Does TestContainers Work?

Containers are:

Note: This is a simplification of the definition of software containerization and does not aim to be a thorough explanation of how the process works.
TestContainers allows defining dependencies as code, and when the code is executed, it takes care of initializing the containers, executing the tests and cleaning the environment afterwards.

This allows you to run the tests in an isolated environment, where you don’t have to worry about having the dependencies installed, just being able to run the tests and to execute containers in your environment.

When to Use TestContainers?

There are many opportunities to use TestContainers, and it does not require for the entire tech stack to be Dockerized, as it can be used to run only parts of the tech stack in a container.

Examples of when TestContainers can be used:

  1. When you have a group of containers in the application that work together through a docker network (with or without Docker Compose)
  2. When there is a group of microservices that runs in containers and you want to run them in a temporal environment.
  3. When you have tests that require a database connection.
  4. When you want to run end to end tests with tools such as Cypress, Playwright or Selenium against a Node.js API that can be run in a container.
  5. When you want to do Load or Performance testing in an isolated environment.

These are only a few examples of scenarios where TestContainers can be useful.

How to Run the Containers?

There are multiple ways to run a container or a group of containers:

Modules

TestContainers provides a series of Modules with built-in configuration for a “plug-and-play” setup of these modules. This list is not the same for all languages; to see the available options, visit https://testcontainers.com/. Since TestContainers is open source, this list can be increased with community collaborations. Example of modules available:

The list of modules available for Java is extensive. There are 44 modules available, which includes 22 modules for Databases. (For more information, see https://java.testcontainers.org/ > modules.)
Example of a Module being used in .NET:

Source: https://github.com/andonyns/Testcontainers-dotnet-demo/blob/test-containers/CustomerService.Tests/CustomerServiceTest.cs#L7

This creates a PostgreSQL container ready to connect and use a real database running in a container.

Generic Containers

A generic container (also called a docker container in some TestContainers implementations) is a container generated for any image available in your local machine, Docker Hub or any other container registry (such as Azure Container Registry or Amazon ECR).

Example of a Generic Container in Python:

Source: https://testcontainers-python.readthedocs.io/en/latest/core/README.html#testcontainers.core.container.DockerContainer
This example uses the hello-world Docker sample image as an example of running a Generic Container.

Build the Dockerfile

You can build a local Dockerfile and run the tests against that file. This is equivalent to building the image locally and running a Generic Container, therefore not all languages have this functionality.

Example of building a Dockerfile in Node.js:

Source: https://node.testcontainers.org/features/images/

From Docker Compose

When you have a docker-compose.yml file, you can build the containers and this will automatically create the network for all containers to be able to communicate with each other.

This gives you less control than creating the containers individually and creating a network between them, although the configuration is very simplified.

Source: https://github.com/andonyns/TestContainers-py-demo/blob/docker-compose/test_web.py

Examples

.NET

Running integration tests against a Postgres database in .NET

“Common” Flow:

https://github.com/andonyns/Testcontainers-dotnet-demo/tree/unit-tests contains an example of a .NET application to handle customers from a Postgres database.
In order to run the tests, you need the following:

  • Have dotnet 7 installed.
  • Have a PostgreSQL database installed and configured to allow connections.
  • Run dotnet build to build the application.
  • Run dotnet test to execute the tests.

This has the following considerations:

  1. To be able to run the tests, there’s a set of prerequisites to set up and a learning curve.
  2. The DBMS system installation adds an extra layer of setup that can be complicated.
  3. In this case, the tests intentionally only make the insertion but do not clean the database afterwards, which will leave the environment dirty and the next test execution will fail because the customers already exist.

Alternative:
Branch https://github.com/andonyns/Testcontainers-dotnet-demo/tree/test-containers demos how to use TestContainers in this case, where we use the PostgreSQL Module.

Source: https://github.com/andonyns/Testcontainers-dotnet-demo/blob/test-containers/CustomerService.Tests/CustomerServiceTest.cs#L7
Line 1 imports the PostgreSQL module.
Line 7 sets up the PostgreSQL Docker container.
Line 13 starts the container.
Line 18 removes the container after the tests are finished.
This has the following advantages:

  1. It’s not necessary to set up the DBMS to execute the tests.
  2. It’s easier to get started with the repo, just clone, install and go, without any additional configuration.
  3. The environment is automatically cleaned after a test execution, which guarantees that the tests will always be run in a clean environment.

Python

Running end-to-end tests with playwright locally against a container with Python and Flask.

“Common” Flow:

https://github.com/andonyns/TestContainers-py-demo contains a simple website with a Hello World message printed.
It also has a playwright test to verify the content of the website.
In order to run the tests, you need the following:

  • Run pip install -r requirements.txt to install the dependencies.
  • Run flask run to start the website.
  • Run pytest to execute the playwright tests.

This has the following considerations:

  1. You need to have python and pip installed and configured, this depends on the Operating System and the chosen setup.
  2. If the website is not running, the tests will fail.

Alternative:
Branch https://github.com/andonyns/TestContainers-py-demo/tree/docker-compose has an example of using TestContainers to run the flask website in a Docker container.

Note: This example uses Docker Compose just to show its functionality. For this scenario, it’s not necessary to have a Docker Compose file for a single container.
In order to run these tests, we need to add the TestContainers dependency, in this case, it’s only a matter of adding the dependency to the requirements.txt file.
In file test_web.py, we added a couple of lines for the Tests to setup the TestContainers:


Source: https://github.com/andonyns/TestContainers-py-demo/blob/docker-compose/test_web.py#L8

Line 3 imports the Docker Compose module from TestContainers.
Line 8 sets the container to the docker-compose file.
Line 13 starts the containers.
Line 14 awaits for the website to be up and running before running the tests.

The docker-compose file does the manual setup described before:


Source: https://github.com/andonyns/TestContainers-py-demo/blob/docker-compose/sample-app/Dockerfile

When running these tests, in your local Docker you will see the container start; then the tests will run and at the end, the environment will be cleaned automatically.

Conclusion

TestContainers can be a valuable addition to a project to isolate the test executions, but not limited only to that, since it can provide value in other scenarios even for local development.

This tool has a big community in the GitHub repos and a slack chat to ask questions and help others. It’s growing and its use will continue increasing as it covers a wide range of scenarios, as the documentation mentions; this can be used with anything that can run in a Docker container.

As an example of its growth, TestContainers was acquired by Docker at the end of 2023. This shows the value and usefulness of this tool, as well as the growth that is having and will continue to have in the future, while staying open source.

More Information:

See a demo of this article in a live talk (in Spanish): QACON 2023 -Isolate your test executions with TestContainers — Andony Núñez

More from the author:

Jest more than unit testing — This shows a short demo of using TestContainers with Jest.
GitHub Actions continuous everything — Using GitHub Actions.

TestContainer demos:

Mastering Testcontainers for Better Integration Tests — TestContainers fundamentals and demo with Spring Boot
Testcontainers — From Zero to Hero. By @MarcoCodes
Getting Started with Testcontainers for .NET
Getting Started with Testcontainers for Go