Testcontainers
SpringBootTests
Java
IntegrationTesting
SoftwareDevelopment

How to reuse Testcontainers between multiple SpringBootTests?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Spring Boot integration tests can become slow when each test class starts fresh containers for the same dependencies. Reusing Testcontainers across multiple test classes can cut minutes from test time, but it must be done carefully to avoid state leakage. The reliable pattern is a shared static container definition plus explicit data reset between tests.

Reuse Strategy Options

You usually choose one of these approaches:

  • Static singleton container started once per JVM test run.
  • Testcontainers reuse mode with withReuse(true) for local development.
  • No reuse in CI for maximum isolation.

Most teams combine static singleton for CI speed and optional reuse mode locally for fast iterative development.

Base Test Class with Static Container

Create one abstract base class that owns container lifecycle and dynamic Spring properties.

java
1import org.springframework.test.context.DynamicPropertyRegistry;
2import org.springframework.test.context.DynamicPropertySource;
3import org.testcontainers.containers.PostgreSQLContainer;
4import org.testcontainers.junit.jupiter.Container;
5import org.testcontainers.junit.jupiter.Testcontainers;
6
7@Testcontainers
8public abstract class AbstractIntegrationTest {
9
10    @Container
11    static final PostgreSQLContainer<?> POSTGRES =
12            new PostgreSQLContainer<>("postgres:16-alpine")
13                    .withDatabaseName("appdb")
14                    .withUsername("app")
15                    .withPassword("secret");
16
17    static {
18        POSTGRES.start();
19    }
20
21    @DynamicPropertySource
22    static void registerProperties(DynamicPropertyRegistry registry) {
23        registry.add("spring.datasource.url", POSTGRES::getJdbcUrl);
24        registry.add("spring.datasource.username", POSTGRES::getUsername);
25        registry.add("spring.datasource.password", POSTGRES::getPassword);
26    }
27}

All integration test classes extend this base class and share the same running container.

Example Test Classes Using Shared Container

java
1@SpringBootTest
2class CustomerRepositoryIT extends AbstractIntegrationTest {
3
4    @Autowired CustomerRepository repository;
5
6    @Test
7    void savesCustomer() {
8        repository.save(new Customer(null, "Ava"));
9        assertThat(repository.count()).isEqualTo(1);
10    }
11}
12
13@SpringBootTest
14class OrderRepositoryIT extends AbstractIntegrationTest {
15
16    @Autowired OrderRepository repository;
17
18    @Test
19    void savesOrder() {
20        repository.save(new Order(null, 1001L, 42.0));
21        assertThat(repository.count()).isEqualTo(1);
22    }
23}

Container startup cost is paid once instead of per class.

Keep Tests Isolated Even with Reuse

Container reuse does not mean data reuse should leak between tests. Add cleanup per test method or class.

java
1@Autowired JdbcTemplate jdbc;
2
3@BeforeEach
4void resetDb() {
5    jdbc.execute("TRUNCATE TABLE orders, customers RESTART IDENTITY CASCADE");
6}

Alternative options include transactional rollback for repository tests, but explicit truncate is clearer for mixed service-level integration tests.

Local Developer Reuse Mode

Testcontainers can keep containers alive across separate test runs. This is useful locally but usually disabled in CI.

~/.testcontainers.properties:

properties
testcontainers.reuse.enable=true

Container definition:

java
static final PostgreSQLContainer<?> POSTGRES =
        new PostgreSQLContainer<>("postgres:16-alpine")
                .withReuse(true);

Important: local reuse can keep stale state if cleanup is incomplete. Use it only when your tests include strict reset logic.

CI Recommendations

In CI environments, prefer deterministic isolation:

  • Disable global reuse mode.
  • Use static container per test JVM run.
  • Run cleanup SQL before each test.
  • Keep test order independent.

This balances speed and reliability without hidden cross-run contamination.

Spring Context Caching Interaction

Spring already caches application contexts between tests with matching configuration. Container reuse works best when test classes share the same Spring profile and property set. If each class changes properties drastically, context cache misses can dominate runtime even if container startup is fast.

Try to align integration test configuration to maximize both context reuse and container reuse.

When Not to Reuse

Do not reuse containers for tests that verify bootstrap migrations against empty databases, test destructive schema operations, or rely on strict startup ordering behavior. In those cases, isolated per-test-class containers provide clearer guarantees even if execution is slower.

Common Pitfalls

  • Reusing containers without cleaning database state between tests.
  • Enabling withReuse(true) in CI and creating flaky cross-run behavior.
  • Starting containers in each test class instead of a shared base class.
  • Changing Spring properties per class and defeating context caching benefits.
  • Assuming shared containers guarantee deterministic tests without data reset.

Summary

  • Share static Testcontainers instances across Spring Boot test classes.
  • Register container connection properties through @DynamicPropertySource.
  • Reset data explicitly to preserve test isolation.
  • Use Testcontainers global reuse only for local developer speedups.
  • In CI, prioritize reproducibility with controlled lifecycle and cleanup.

Course illustration
Course illustration

All Rights Reserved.