Spring Boot 3.1.0 introduced great support for Testcontainers that’ll not only make writing integration tests easier, but also make local development a breeze.
“Clone & Run” Developer Experience
Gone are the days of maintaining a document with a long list of manual steps needed to set up an application locally before running it. With Docker installing the application, dependencies became easier. But you still had to maintain different versions of scripts based on your Operating System, to manually spin up the application dependencies as Docker containers.
With the Testcontainers support added in Spring Boot 3.1.0, developers can now simply clone the repository and run the application! All the application dependencies, such as databases, message brokers, etc. can be configured to automatically start when we run the application.
If you’re new to Testcontainers, go through Getting started with Testcontainers in a Java Spring Boot Project guide to learn how to test your Spring Boot applications using Testcontainers.
Simplified integration testing using ServiceConnections
Prior to Spring Boot 3.1.0, we had to use @DynamicPropertySource
to set the dynamic properties obtained from containers started by Testcontainers as follows:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
class CustomerControllerTest {
@Container
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:15-alpine");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
// your tests
}
Then, Spring Boot 3.1.0 introduced the new concept of ServiceConnection. This automatically configures the necessary Spring Boot properties for the supporting containers.
First, add the spring-boot-testcontainers
as a test dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-testcontainers</artifactId>
<scope>test</scope>
</dependency>
Now, we can rewrite the previous example by adding @ServiceConnection
without having to explicitly configure spring.datasource.url
, spring.datasource.username
, and spring.datasource.password
using the @DynamicPropertySource
approach.
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
class CustomerControllerTest {
@Container
@ServiceConnection
static PostgreSQLContainer<?> postgres =
new PostgreSQLContainer<>("postgres:15-alpine");
// your tests
}
Notice that we’re not registering the datasource properties explicitly anymore.
The @ServiceConnection
support not only works for relational databases but also many other commonly used dependencies like Kafka, RabbitMQ, Redis, MongoDB, ElasticSearch, and Neo4j. For the complete list of supporting services, see the official documentation.
You can also define all your container dependencies in one TestConfiguration class and import it into your integration tests.
For example, let’s say you’re using Postgres and Kafka in your application. You can then create a class called ContainersConfig
as follows:
@TestConfiguration(proxyBeanMethods = false)
public class ContainersConfig {
@Bean
@ServiceConnection
public PostgreSQLContainer<?> postgreSQLContainer() {
return new PostgreSQLContainer<>("postgres:15.2-alpine");
}
@Bean
@ServiceConnection
public KafkaContainer kafkaContainer() {
return new KafkaContainer(
DockerImageName.parse("confluentinc/cp-kafka:7.2.1"));
}
}
Finally, you can import the ContainersConfig
into your tests as follows:
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Import(ContainersConfig.class)
class ApplicationTests {
//your tests
}
How to use a container that doesn’t have ServiceConnection support
In your applications, you may need to use a dependency that doesn’t have a dedicated Testcontainers module or out-of-the-box ServiceConnection support from Spring Boot. Don’t worry, you can still use Testcontainers GenericContainer
and register the properties using DynamicPropertyRegistry
.
For example, you might want to use Mailhog for testing email functionality. In this case, you can use Testcontainers GenericContainer
and register Spring Boot email properties as follows:
@TestConfiguration(proxyBeanMethods = false)
public class ContainersConfig {
@Bean
@ServiceConnection
public PostgreSQLContainer<?> postgreSQLContainer() {
return new PostgreSQLContainer<>("postgres:15.2-alpine");
}
@Bean
@ServiceConnection
public KafkaContainer kafkaContainer() {
return new KafkaContainer(
DockerImageName.parse("confluentinc/cp-kafka:7.2.1"));
}
@Bean
public GenericContainer mailhogContainer(DynamicPropertyRegistry registry) {
GenericContainer container = new GenericContainer("mailhog/mailhog")
.withExposedPorts(1025);
registry.add("spring.mail.host", container::getHost);
registry.add("spring.mail.port", container::getFirstMappedPort);
return container;
}
}
As we’ve seen, we can use any containerized service and register the application properties.
Local development using Testcontainers
In the previous section, we learned how to use Testcontainers for testing Spring Boot applications. With Spring Boot 3.1.0 Testcontainers support, we can also use Testcontainers during the development time to run the application locally.
To do this, create a TestApplication
class in the test classpath under src/test/java as follows:
import org.springframework.boot.SpringApplication;
public class TestApplication {
public static void main(String[] args) {
SpringApplication
.from(Application::main) //Application is main entrypoint class
.with(ContainersConfig.class)
.run(args);
}
}
Observe that we’ve used the configuration class ContainersConfig
using .with(...)
to attach it to the application launcher.
Now you can run TestApplication
from your IDE. It will automatically start all the containers defined in ContainersConfig
and configure the properties.
You can also run TestApplication
using the Maven or Gradle build tools as follows:
./mvnw spring-boot:test-run //Maven
./gradlew bootTestRun //Gradle
Using DevTools with Testcontainers at development time
We’ve now learned how to use Testcontainers for local development. But one challenge with this setup is that every time the application is modified and a build is triggered, the existing containers will be destroyed and new containers will be created. This can result in slowness or loss of data between application restarts.
Spring Boot provides devtools to improve the developer experience by refreshing the application upon code changes. We can use @RestartScope
annotation provided by devtools to indicate certain beans to be reused instead of recreating them.
First, let’s add the spring-boot-devtools dependency as follows:
<!-- For Maven -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- For Gradle -->
testImplementation "org.springframework.boot:spring-boot-devtools"
Now, add @RestartScope
annotation on bean definitions in ContainersConfig
as follows:
@TestConfiguration(proxyBeanMethods = false)
public class ContainersConfig {
@Bean
@ServiceConnection
@RestartScope
public PostgreSQLContainer<?> postgreSQLContainer() {
return new PostgreSQLContainer<>("postgres:15.2-alpine");
}
@Bean
@ServiceConnection
@RestartScope
public KafkaContainer kafkaContainer() {
return new KafkaContainer(
DockerImageName.parse("confluentinc/cp-kafka:7.2.1"));
}
...
}
Now if you make any application code changes and the build is triggered, the application will restart but use the existing containers.
Please note: Eclipse automatically triggers a build when the code changes are saved, while in IntelliJ IDEA, you need to trigger a build manually.
Conclusion
Modern software development involves using lots of technologies and tools to tackle growing business needs. This has resulted in a significant increase in the complexity of the development environment setup. Improving the developer experience isn’t a nice to have anymore — it’s a necessity.
To improve this developer experience, Spring Boot 3.1.0 added out-of-the-box support for Testcontainers. Spring Boot and Testcontainers integration works seamlessly with your local Docker, on CI, and with Testcontainers Cloud too.
This is an impactful transformation for not only testing but also local development. And developers can now look forward to an experience that brings the clone & run philosophy into reality.
Learn more
- Sign up for a Testcontainers Cloud account.
- Connect on the Testcontainers Slack.
- Learn about Testcontainers best practices.
- Get started with the Testcontainers guide.
- Subscribe to the Docker Newsletter.
- Have questions? The Docker community is here to help.
- New to Docker? Get started.
Feedback
0 thoughts on "Spring Boot Application Testing and Development with Testcontainers"