Why do two @SpringBootTest classes importing the same Testcontainers config share one container set?
answer
- Contexts are cached, not test classes
- The key is the merged configuration
- Containers are beans in that context
- Extra properties or profiles fork the key
- Cache is bounded; eviction closes the context
basics
~20 sSpring's TestContext framework caches application contexts by a key built from their merged configuration. Two classes with identical configuration — same imports, profiles, properties — reuse the cached context, so its container beans start once and serve both.
solid answer
~50 sContexts are cached per key, and the key is the *merged context configuration*: the configuration and component classes (which `@Import` contributes to), active profiles, property sources such as `@TestPropertySource`, initializers, and registered context customizers. Two classes whose keys match get the identical `ApplicationContext` instance, so the container beans inside it were already started by the first class and are simply reused. Change anything that feeds the key — a different `@Import`, an extra inline property, `@ActiveProfiles`, a `@MockitoBean` — and you get a second context and a second set of containers. Two forces then bite: startup time multiplies, and the cache is bounded (32 contexts by default, `spring.test.context.cache.maxSize`), so evicting a context closes it and stops its containers. `@DirtiesContext` does the same on purpose. Keeping a suite on one shared configuration is therefore a performance decision, not a style one.
code
java · 13 lines@SpringBootTest
@Import(TestcontainersConfiguration.class)
abstract class AbstractIntegrationTests {
}
// same merged configuration as the base class -> cached context reused
class OrderRepositoryTests extends AbstractIntegrationTests { }
class PaymentRepositoryTests extends AbstractIntegrationTests { }
// different key -> second context, second set of containers
@SpringBootTest(properties = "app.retry.enabled=true")
@Import(TestcontainersConfiguration.class)
class RetryPolicyTests { }go deeper
Know that Spring reuses an application context between test classes when their configuration matches, and that this is why a container often starts only once for a whole suite.
Explain the cache key: configuration and component classes, profiles, property sources, initializers and customizers. Name the annotations that fork it, and connect one context to one set of container beans.
Diagnose a fragmented suite from its symptoms — repeated container startups, ballooning wall-clock time — trace it to the annotations splitting the key, and converge the suite on a shared base configuration.
Set the policy: a single shared integration-test configuration, a small allowed number of context shapes, a considered cache size, and a rule for when a test may legitimately fork the context at all.
## What is being cached Spring's TestContext framework never rebuilds an application context it has already built for an equivalent test configuration. It keeps a cache of contexts keyed by a `MergedContextConfiguration` — an object assembled from everything that determines what the context contains. The inputs to that key include: the set of classes and locations that define the context (this is where `@SpringBootTest`'s discovered application class and any `@Import`-ed configuration classes land), active profiles from `@ActiveProfiles`, property sources from `@TestPropertySource` and `@SpringBootTest(properties = ...)`, `ApplicationContextInitializer`s, the web environment mode, the parent context, and the set of `ContextCustomizer`s that Spring Boot contributes. If two test classes produce equal keys, they get the *same object* — one `ApplicationContext`, one set of singletons, one set of running containers. ## Why containers ride along When containers are declared as `@Bean` methods in a configuration class, they are singletons in that context. Their lifecycle is the context's lifecycle: started when the context starts, stopped when it closes. So "how many contexts does my suite create?" and "how many database containers does my suite start?" become the same question. That is the load-bearing insight, and it is what an interviewer is listening for. A candidate who describes container count as a property of *test classes* has the model wrong; it is a property of *distinct context configurations*. ## What splits the cache In practice, suites fragment for a handful of recurring reasons: - **Inconsistent imports.** Half the classes import `TestcontainersConfiguration`, half import a slightly different one, or add a second configuration for one test's stub. - **Per-class inline properties.** `@SpringBootTest(properties = "feature.x.enabled=true")` on one class alone produces a distinct key and a whole second context. - **Profiles.** `@ActiveProfiles("integration")` on some classes only. - **Mock bean annotations.** `@MockitoBean` (and the older `@MockBean`) register a context customizer that becomes part of the key, deliberately, because the context genuinely differs. Sprinkling them broadly is a common cause of context explosion. - **`@DirtiesContext`.** This does not split the key; it evicts the entry, so the next test rebuilds — and the closed context stops its containers on the way out. ## The bounded cache The cache holds a limited number of contexts — 32 by default, tunable with the `spring.test.context.cache.maxSize` property — and evicts on a least-recently-used basis. Eviction *closes* the context, which stops its container beans. A suite with many distinct configurations can therefore thrash: contexts (and containers) are built, evicted, and rebuilt within a single run, which shows up as wildly variable suite time and, on constrained machines, memory pressure from the contexts that are still cached. Spring logs cache statistics (hits, misses, size) at debug level for `org.springframework.test.context.cache`; turning that on is the fastest way to see how many distinct contexts a suite really builds. ## Designing for one context The practical pattern is convergence: a single shared `@TestConfiguration` holding every container the suite needs, imported from one abstract base class that all integration tests extend, with no per-class properties or profiles unless there is a real reason. Divergent needs are better expressed inside the shared context — a bean that is configurable at runtime, or a test that sets state rather than configuration — than by forking the configuration. When divergence is unavoidable, aim for a small number of *shapes* (say two or three configurations) rather than a long tail of one-off keys, and be aware that the total is bounded by the cache size. ## Interaction with container reuse Cache sharing keeps containers alive within one JVM run. Testcontainers' own reuse feature is the complementary lever across runs. They solve different halves of the problem, and the context-cache half is the one you control purely through how you annotate tests. ## Signals of a fragmented suite Suite wall-clock time far exceeding the sum of the test work; repeated "Starting container" log lines for the same image; `Creating new ApplicationContext` appearing many times; memory growth across a run. Each points at key fragmentation rather than at a slow container.
- What does @DirtiesContext cost in a Testcontainers-backed suite?It evicts the cached context, and closing that context stops its container beans. The next test that needs the same configuration rebuilds the context and restarts the containers. Use it only when a test genuinely corrupts context state; cleaning data between tests is nearly always cheaper.
- How would you find out how many contexts your suite actually builds?Enable debug logging for org.springframework.test.context.cache — Spring reports cache hits, misses and size as the suite runs. Rising misses and repeated container startup log lines for the same image tell you which annotations are forking the key.
- Does adding @MockitoBean to one test class affect the rest of the suite?Not the others directly, but that class gets its own context because the mock registers a context customizer that becomes part of the cache key. So it starts its own containers, and it occupies a cache slot that may push another context out.
saying these in an interview costs you the question
- Thinks each test class always gets its own container
- Adds @SpringBootTest properties per class without cost awareness
- Uses @DirtiesContext routinely to isolate tests
- Believes the context cache is unbounded
- Blames slow suites on container startup rather than context count