Unit testing in Java is made simple by libraries such as JUnit and e.g. Mockito, but there’s not the same panacea for integration testing. Alongside your mocked out unit tests, integration tests are important to ensure the correct function of your software when accessing external services. Previously, we have taken different approaches to the problem, such as:
- Embedding the service
- Running the service
- Running a Docker container for the service
Embedding the service can be hard (especially if it is not Java). Running them directly, or remotely, adds significant work to configure, maintain and make available for all developers and your continuous integration service. Running a Docker container for the service was our preferred method but setting them up locally and as part of the CI build seems out of sorts with the fundamental approach to testing. This has been a common and frustrating pain point for us.
Test Containers
We have recently switched to using TestContainers to solve this problem. TestContainers is an MIT licensed open source project for running a Docker container for a JUnit test.
Benefits
This has a number of benefits over our previous approaches. First, we are running the tests against a real instance of the service, not some mocked or embedded version, which may behave differently. As the containers run just for the test they are isolated from other test and become much more reproducible. The setup and configuration are done in the test class, where they belong. It also allows you to easily repeat the test against different versions of the service allowing you to correctly determine a compatibility and check new versions of services do not require changes to your own code.
Use cases
This technique for integration testing can easily be applied to multiple use cases, some of which have more specialised support:
- Databases
- Logging services
- Web services
- Complex multi-service interactions using Docker Compose
- UI testing with Selenium
Example
The following example shows how to set up an integration test for Redis.
import org.junit.Rule;
import org.junit.Test;
import org.testcontainers.containers.GenericContainer;
import redis.clients.jedis.Jedis;
public class RedisIntegrationTest {
private static final String DOCKER_IMAGE = "redis:3.2.9";
private final Jedis jedis;
@Rule // 1
public static GenericContainer redis = new GenericContainer(DOCKER_IMAGE).withExposedPorts(6379); // 2, 3
@Before
public void setUp() throws Exception {
Jedis jedis = new Jedis(redis.getContainerIpAddress(), redis.getMappedPort(6379)); // 4
}
@Test
public void yourTest() {
//Your test code here
}
}
-
@Rule
Runs a new container for every test, you can change to@ClassRule
for a single container for the test class. - It also supports the use of Dockerfiles or Docker Compose files for non-standard, of not published containers by using
DockerComposeContainer
. - Other standard docker config like
.withVolume(), withEnv(), withCommand()
are available. - Obtain the configuration you need from the
GenericContainer
object.
Disadvantages
None! O.K. maybe there are some disadvantages, but we think they are well worth it:
- You need docker available to run the tests. (i.e. on your local device and on the CI server) but who doesn’t?
- You need to make docker available (which may require exposing the docker socket)
- You have to download the image but this is only needed once.
- Windows support is only in Alpha.
Credit: Image by Richard North, TestContainers Project.
Top comments (0)