BumbleByte Software

Independent Software Contracting

Turning Docker Compositions into Test Suites

Use Docker compositions in tests with Test-Containers and ScalaTest.

Sample code repository.

We use docker-compose as a tool that can build, deploy, and manage multiple containers on the same network. These configurations, known as compositions, remove a lot of the manual setup we often associate with running infrastructure locally. It provides local versions of dependencies for a faster, more realistic feedback loop when developing applications.

Our application level tests could look as simple as this;

Test Containers

For our tests we will use test-containers. It offers us;

Test-Containers has a Docker-Compose plugin that allows us to run compositions from our tests. First, let’s define a simple composition file.

I’ve kept it simple by only including our application, but from this point on I can extend it with whatever setup we need without having to change our tests at all. To start using this in our tests we’ll need to do three things.

  1. Build and publish our application image locally.
  2. Integrate our composition setup into our test-suite.
  3. Create a client that can call our application.

1. Build and publish our application image locally.

Before we can use it in a test suite we need to build our application into a Docker image. Unfortunately Docker-Compose does not have a concept of running external build tasks to get an image. That said most build tools will allow us to build an image before running our test suite. With SBT and native-packager plugin makes it easy to add our Docker build stage as a prerequisite for our integration tests.

An SBT quirk means we need to specify our Docker build step for every test task.

    Test / test := (Test / test).dependsOn(server / Docker / publishLocal).value,
    Test / testOnly := (Test / testOnly).dependsOn(server / Docker / publishLocal).evaluated,
    Test / testQuick := (Test / testQuick).dependsOn(server / Docker / publishLocal).evaluated

2. Integrate our composition setup into our test-suite.

Next, we add the image details to our tests.

The Test-Containers library has integrations for many test frameworks, which makes this easier, but we’ll still need to do some wiring. The Docker-Compose module needs to know the location of our compose-file. You might want to hard-code this but that affects the portability of your tests. Instead, I recommend passing the location in as a property and using SBT to locate the file. This has the benefits us by enabling testing against multiple compose-files; useful for testing against different environments or setups.

If you use an IDE to run test-suites you may need to update the runner config before this will work. In IntelliJ, you can save a run configuration and share it by committing it to the repo.

We need to wait for our containers to start before using them. We can get around this by making our access details lazy or use the Test-Containers helpers (which run after the containers finish starting).

Lastly we need to instruct Test-Containers on how to use our containers.

Some additional considerations you might make;

3. Create a client to call our application.

Finally let’s create our HTTP client to call our service. We have full access to the Docker containers and, if needed, we can connect to our dependencies directly. Alternatively if you provide clients for your service you can use them here instead.

Summary

With our test fixture code in place we can now start writing code without too much concern about the state of our containers.

Now if we hit a test failure we can launch our docker-composition to debug our test case. This helps us switch to an iterative, live feedback loop for manual testing and experimentation.