Testcontainers With Spring Boot For Integration Testing

Take your Integration Tests to next level using Testcontainers

Now days the modern application interacts with multiple systems like database, microservice within system ,external API, middleware systems etc.It’s become very critical for the success of project to have good Integration test strategy.
In memory database like H2, HSQLDB are very popular for Integration testing of persistance layer but these are not close to production environment. If application has dependancy on Docker containers, Then it’s become more difficult to test that code in Integration environment. Testcontainers help us to handle these challenges

What is Testcontainers?

Testcontainers is a Java 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.

The code for this post is available on my Github account here

Let’s Write some Integration Test using Testcontainers For Spring Boot App

In previous Post We created simple Spring Boot application that uses Mongodb Database (containrized) let’s write integration test for that.

It’s easy to add Testcontainers to your project - let’s walk through a quick example to see how.

Add Testcontainer to project

Pom File
1
2
3
4
5
6
7
8
9
10
11
12
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.12.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.12.3</version>
<scope>test</scope>
</dependency>

Creating a generic container based on an image

For Mongodb there is no special test container image available, But we can create one by extending GenericContainer

MongoDbContainer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MongoDbContainer extends GenericContainer<MongoDbContainer> {

public static final int MONGODB_PORT = 27017;
public static final String DEFAULT_IMAGE_AND_TAG = "mongo:3.2.4";
public MongoDbContainer() {
this(DEFAULT_IMAGE_AND_TAG);
}
public MongoDbContainer(@NotNull String image) {
super(image);
addExposedPort(MONGODB_PORT);
}
@NotNull
public Integer getPort() {
return getMappedPort(MONGODB_PORT);
}
}

You can also create generic container using ClassRule
1
2
3
4

@ClassRule
public static GenericContainer mongo = new GenericContainer("mongo:3.2.4")
.withExposedPorts(27017);

Starting the container and using it in test

MongoDbContainerTest
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@SpringBootTest
@AutoConfigureMockMvc
@ContextConfiguration(initializers = MongoDbContainerTest.MongoDbInitializer.class)
@Slf4j
public class MongoDbContainerTest {

@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private FruitRepository fruitRepository;

private static MongoDbContainer mongoDbContainer;

@BeforeAll
public static void startContainerAndPublicPortIsAvailable() {
mongoDbContainer = new MongoDbContainer();
mongoDbContainer.start();
}


@Test
public void containerStartsAndPublicPortIsAvailable() throws Exception {

FruitModel build = FruitModel.builder().color("Red").name("banana").build();
mockMvc.perform(post("/fruits")
.contentType("application/json")
.content(objectMapper.writeValueAsString(build)))
.andExpect(status().isCreated());
Assert.assertEquals(1, fruitRepository.findAll().size());
}

public static class MongoDbInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
log.info("Overriding Spring Properties for mongodb !!!!!!!!!");

TestPropertyValues values = TestPropertyValues.of(
"spring.data.mongodb.host=" + mongoDbContainer.getContainerIpAddress(),
"spring.data.mongodb.port=" + mongoDbContainer.getPort()

);
values.applyTo(configurableApplicationContext);
}
}
}
}

lets see what we are doing in test line by line.
@SpringBootTest As we want to write Integration test we are using @SpringBootTest which tells Spring to load complete application context.

@AutoConfigureMockMvc configure auto-configuration of MockMvc. As we want to test
controller->service->repository ->database

@ContextConfiguration Overriding Spring properties. We need to override host and port on which mongodb testcontainer has started.

Starting Mongodb container in BeforeAll hook so that DB instance is available during the test.
Then In test we are simpaly calling post method on controller and after that checking if actually data is getting inserted in database or not.

Using Predefined Testcontainer

There are some predefined Testcontainer are available, Which are very useful and easy to use
e.g MySQLContainer is available for mysql database. let’s see how to use that

MySQLContainerTest
1
2
3
4
5
6
7
8
9
10
11
12
@Testcontainers
public class MySQLContainerTest {

@Container
private final MySQLContainer mySQLContainer = new MySQLContainer();

@Test
@DisplayName("Should start the container")
public void test() {
Assert.assertTrue(mySQLContainer.isRunning());
}
}

To use the Testcontainers extension annotate your test class with @Testcontainers.

Note You need to think about how you want to use container

  1. Containers that are restarted for every test method
  2. Containers that are shared between all methods of a test class

If you define Container like this
@Container private MySQLContainer mySQLContainer = new MySQLContainer(); Then it will create new instance for each test case and it you use static like this
@Container private static MySQLContainer mySQLContainer = new MySQLContainer(); then same Instance will be used for all tests.

The code for this post is available on my Github account here

Share Comments