Streamlining Spring Boot Integration Tests with Testcontainers

Master Spring Ter
3 min readNov 11, 2024

ntegration testing made easy with Spring Boot and Testcontainers

Introduction

Integration testing is a crucial aspect of modern software development. It ensures that different components of your application interact seamlessly. However, managing external dependencies like databases or message brokers during testing can be challenging. Setting up dedicated test environments or using in-memory databases may not accurately reflect your production systems.

Enter Testcontainers — a Java library that provides lightweight, disposable instances of common databases, message brokers, or anything else that can run in a Docker container. In this article, we’ll explore how to use Testcontainers with Spring Boot to simplify integration testing. We’ll focus on a practical example using a Redis container.

Features of Spring Boot Testcontainers

  • Lightweight and Disposable Containers: Spin up Docker containers for your tests and destroy them afterward, ensuring a clean state every time.
  • Seamless Integration with JUnit 5: Annotations like @Testcontainers and @Container make it easy to manage the lifecycle of your containers.
  • Support for Various Technologies: Testcontainers support a wide range of databases, message brokers, and other technologies.

Setting Up Testcontainers in a Spring Boot Project

1. Adding Dependencies

First, include the necessary dependencies in your pom.xml or build.gradle file.

For Maven:

<dependencies>
<!-- Spring Boot Starter Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Testcontainers for JUnit 5 -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.20.3</version>
<scope>test</scope>
</dependency>
<!-- Testcontainers Redis Module -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>redis</artifactId>
<version>1.20.3</version>
<scope>test</scope>
</dependency>
<!-- Spring Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>

For Gradle:

dependencies {
// Spring Boot Starter Test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
// Testcontainers for JUnit 5
testImplementation 'org.testcontainers:junit-jupiter:1.20.3'
// Testcontainers Redis Module
testImplementation 'org.testcontainers:redis:1.20.3'
// Spring Data Redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
}

2. Enable JUnit 5

Ensure that your project is set up to use JUnit 5, as Testcontainers integrates seamlessly with it.

Best Practices

  • Reuse Containers When Appropriate: Starting and stopping containers can be time-consuming. Consider reusing containers for multiple tests when possible.
  • Use @DynamicPropertySource: This annotation allows you to programmatically set properties in the Spring Environment before the context is refreshed.
  • Proper Cleanup: Although Testcontainers handles cleanup automatically, ensure that any resources are properly released to prevent memory leaks.

Example: Integration Testing with Redis Container

Let’s dive into a practical example where we’ll set up a Redis container for integration testing.

1. Setting Up the Redis Container

We’ll use the @Testcontainers and @Container annotations to manage our Redis container.

import org.springframework.boot.test.context.SpringBootTest;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.containers.GenericContainer;

@SpringBootTest
@Testcontainers
public class RedisIntegrationTest {

@Container
static GenericContainer<?> redisContainer = new GenericContainer<>("redis:7.0.11-alpine")
.withExposedPorts(6379);

// Tests will go here
}

2. Using @DynamicPropertySource

We need to update the application context with the Redis container’s host and port.

import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;

@SpringBootTest
@Testcontainers
public class RedisIntegrationTest {

@Container
static GenericContainer<?> redisContainer = new GenericContainer<>("redis:7.0.11-alpine")
.withExposedPorts(6379);

@DynamicPropertySource
static void redisProperties(DynamicPropertyRegistry registry) {
String redisHost = redisContainer.getHost();
Integer redisPort = redisContainer.getFirstMappedPort();
registry.add("spring.redis.host", () -> redisHost);
registry.add("spring.redis.port", () -> redisPort);
}

// Tests will go here
}

3. Writing a Test Case

Let’s write a simple test to verify that we can set and get a value from Redis.

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;

import static org.junit.jupiter.api.Assertions.assertEquals;

@SpringBootTest
@Testcontainers
public class RedisIntegrationTest {

@Container
static GenericContainer<?> redisContainer = new GenericContainer<>("redis:7.0.11-alpine")
.withExposedPorts(6379);

@Autowired
private StringRedisTemplate redisTemplate;

@DynamicPropertySource
static void redisProperties(DynamicPropertyRegistry registry) {
String redisHost = redisContainer.getHost();
Integer redisPort = redisContainer.getFirstMappedPort();
registry.add("spring.redis.host", () -> redisHost);
registry.add("spring.redis.port", () -> redisPort);
}

@Test
void testRedisConnection() {
redisTemplate.opsForValue().set("test-key", "Hello, Redis!");
String value = redisTemplate.opsForValue().get("test-key");
assertEquals("Hello, Redis!", value);
}
}

4. Configuration

Ensure that your application.properties or application.yml does not override the Redis connection properties set by @DynamicPropertySource. If you have default Redis configurations, they will be overridden during the test.

Example application.properties:

# Leave Redis configurations empty or comment them out
# spring.redis.host=localhost
# spring.redis.port=6379

Conclusion

Using Testcontainers with Spring Boot simplifies integration testing by providing ephemeral instances of external dependencies like Redis. This approach ensures that your tests are consistent, reliable, and reflective of production environments.

By following best practices and being aware of common pitfalls, you can enhance your testing strategy and ship robust applications with confidence.

References

Happy Testing!

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Master Spring Ter
Master Spring Ter

Written by Master Spring Ter

https://chatgpt.com/g/g-dHq8Bxx92-master-spring-ter Specialized ChatGPT expert in Spring Boot, offering insights and guidance for developers.

No responses yet

Write a response