How to use Testcontainers for hassle-free integration testing
9 January 2019 |
Paweł Szymański | About a 5 minute read
There are times when “mocking the world” is just not enough.
Database queries, third-party software or some API calls just have to be tested against the real thing. This is especially true when we are working with Cloud, as it provides tools as services with which our applications need to integrate. So what do we do when that happens? We spin up Docker, of course!
Docker is a neat tool that makes life a lot easier in many ways. Recently, I worked on a project that reminded me that it can be simplified even further – thanks to Testcontainers. As a huge TDD fan, I started with writing tests and I thought I’d share a quick, worked example of how Testcontainers can remove some of the hassle of integration testing.
Say Hello to Testcontainers
So what exactly is Testcontainers?
“Testcontainers is a Java 8 library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.” (Source: Github)
What that means is that it is possible (and extremely easy) to spin up a Docker container straight from your integration tests. No scripting required – everything is handled by the test code.
A Worked Example
Imagine you want to write some code that integrates with AWS DynamoDB. There are quite a few ways to run this service – or an equivalent – on your machine:
- Download and run DynamoDB jar file.
- Download and run dynalite.
- Run embedded DynamoDB straight from your Maven project.
- Spin up a Docker container.
Unfortunately, most of these solutions require additional dependencies, like SQLite. On top of that, unlike Docker, they are platform-dependent.
In my example, I will be using the amazon/dynamodb-local Docker image.
To make it clean and easy, our sample application will consist of only two classes. The code is in Kotlin, but could be in Java or Scala. Same thing applies to the tests – I have chosen Spock, but Testcontainers can be employed by anything that runs on JVM.
Product and Products Repository
Our application’s purpose is to save `Product` objects:
To a DynamoDB database:
To run the test, we need to add the following Maven dependency:
When testing database queries, and other things that require an expensive setup, I tend to put the setup code in a common base class. Try not to add any more code to the base class than the minimum necessary for setup and cleanup.
Inheritance in tests can be a code smell.
And finally, our very simple test case:
Notice how I am always creating the table and then deleting it after each test case. This is not really necessary with a single test case, but when more features are tested the database will eventually get polluted. It is better to keep it clean so no accidental dependencies are introduced between the test cases.
Also, keep in mind that the entire container, along with the DynamoDB instance, exists only for the duration of tests and will be discarded afterwards. Consecutive test runs should not depend on each other!
Obviously, the Docker daemon needs to be started before you run your tests. If you forgot to start it, the following exception will be thrown:
java.lang.IllegalStateException: Could not find a valid Docker environment. Please see logs and check configuration
To fix it, simply start Docker.
– Testcontainers provide a degree of flexibility for integration tests that’s often overlooked – make the most of them for more hassle-free integration testing.
– You can start one or multiple containers before each test case or only once per tests execution.
– The library is meant to be used for testing (hence the name). If you are looking for a better way to manage your containers, use orchestration tools such as Kubernetes.
– Be mindful of the environment you’re planning to run your tests in. If it is already dockerized, Testcontainers will not be feasible. Using Docker in Docker is usually a bad idea.
Programme Director (Edinburgh)
Bring your expert project knowledge to the table to own delivery of all our initiatives being delivered out of our Delivery EngineI'm Interested
Engineering Director (Edinburgh)
Bring your expert tech knowledge to the table to own key technological and architectural decisions for our Delivery Engine.I'm Interested
Onboarding Co-ordinator (London)
Play a key role in inducting all our new starters through AND’s award winning onboarding experience!I'm Interested