PATTERN Cited by 1 source
Shared static container across tests¶
Pattern¶
Hold a Testcontainers container in
a public static field on an abstract base test class,
start it once in a static initialiser, and have every
concrete integration test extend the base. All ITs share the
same container instance across the JVM process.
Implements the singleton container pattern for JVM integration test suites.
Shape¶
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
public abstract class AbstractIntegrationTest {
public static PostgreSQLContainer postgreSQL =
new PostgreSQLContainer("postgres:13.1")
.withUsername("testUsername")
.withPassword("testPassword")
.withDatabaseName("testDatabase");
static {
postgreSQL.start(); // once per JVM
}
@DynamicPropertySource
static void postgresqlProperties(DynamicPropertyRegistry r) {
r.add("db_url", postgreSQL::getJdbcUrl);
r.add("db_username", postgreSQL::getUsername);
r.add("db_password", postgreSQL::getPassword);
}
}
class AccountRepositoryIntegrationTest extends AbstractIntegrationTest {
@Autowired AccountRepository dao;
@Test
void shouldCreateAccount() { /* uses shared DB */ }
}
Source: sources/2021-02-24-zalando-integration-tests-with-testcontainers
Mechanism¶
staticfield is class-loader-scoped: evaluated once per JVM, shared by every subclass.static { container.start(); }initialiser runs on first class load, before any@BeforeAllfires.@DynamicPropertySource(Spring 5.2.5+) wires dynamic JDBC URL / random-port values into the Spring test context after the container has started. Method references on the container (postgreSQL::getJdbcUrl) defer evaluation until Spring asks.- No
.stop(). Testcontainers registers a JVM shutdown hook; the Ryuk reaper (itself a Docker container) cleans up any orphaned images if the JVM dies before shutdown hooks run.
Contrast with @Testcontainers / @Container¶
The JUnit Jupiter Testcontainers module provides annotation-based lifecycle:
@Testcontainers
public class ApplicationIntegrationTest {
@Container
public static PostgreSQLContainer postgreSQL = new PostgreSQLContainer(...);
}
This is per-class, not per-JVM. With N IT classes you pay N container startups. The Testcontainers docs explicitly note: "containers cannot be reused between test classes" and "this extension has only been tested with sequential test execution". The shared-static-base-class pattern avoids both limitations at the cost of inheritance coupling.
Tradeoffs¶
- Shared DB state across tests. See
FIRST. Two strategies to
keep isolation:
- Unique IDs per test (
UUID.randomUUID(),account-<ts>), no cleanup. Aggregate queries see other tests' rows. @AfterEachcleanup. More developer discipline needed.
- Unique IDs per test (
- Inheritance is sticky. Every IT must extend
AbstractIntegrationTest. Teams that can't use inheritance (shared composition across different test superclasses) need an alternative — often a JUnit 5 extension or a@SingletonSpring bean. - Slow first test. The first test to trigger base-class loading pays the ~4 s Postgres startup (20 s Localstack); subsequent tests are fast.
- One container per container type. To run DB +
Localstack + MockServer, declare three
staticfields on the base. All start sequentially in the static initialiser (or in parallel if you declare them.withStartupTimeout(...)and useStartables.deepStart(...)). - No parallel-test safety inside the JVM. Two tests that write to the same Postgres row race unless isolation rules are enforced.
Alternatives¶
@Containerper class — isolation by construction, pay startup per class.- Testcontainers Desktop /
.withReuse(true)— container persists across JVM runs. Adds new failure modes (stale state between unrelated runs) but cuts first-test latency near zero. Not discussed in Zalando's 2021 post. - Dynamic
@DynamicPropertySourcefrom bean factory — for teams avoiding inheritance; works with JUnit 5 extension model.
Seen in¶
- sources/2021-02-24-zalando-integration-tests-with-testcontainers —
Zalando Marketing Services's canonical
AbstractIntegrationTestbase-class idiom with shared staticPostgreSQLContainer.
Related¶
- systems/testcontainers · systems/spring-boot · systems/junit5
- concepts/singleton-container-pattern — the named pattern this implementation realises.
- concepts/first-test-principles — the properties this pattern puts under stress.
- patterns/real-docker-container-over-in-memory-fake — motivates paying the startup cost in the first place.
- patterns/failsafe-integration-test-separation — the Maven plumbing that runs the ITs selectively.