skip to content

Full-Context Cost & When to Slice

A full context test loads every auto-configuration and possibly a server, so it is slow and it hides which layer actually broke. Being able to argue for a slice instead is the point of the question.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

5

Why is @SpringBootTest considered expensive/slow, and what exactly does it load?

level: juniorimportance: must knowfreq 70%

answer

  1. full ApplicationContext, not a unit
  2. scan + all auto-configs + bean wiring
  3. embedded server only if webEnvironment != MOCK
  4. cost = context build, not test body
  5. fix = caching + slices

basics

~10 s

@SpringBootTest starts the full Spring application context — every bean, all auto-configurations, and (optionally) an embedded web server. Building that whole context takes seconds, so tests run slowly.

solid answer

~40 s

@SpringBootTest boots the complete ApplicationContext the way the real app does: it runs component scanning, instantiates every @Component/@Service/@Repository, applies all matching Spring Boot auto-configurations (DataSource, JPA, security, Jackson, etc.), and — depending on webEnvironment — may start an embedded Tomcat/Netty server on a real or random port. That is a lot of work: reflection, classpath scanning, bean wiring, connection pools, EntityManagerFactory, etc. For a unit-sized assertion you pay a full-application startup cost (often 2–10+ seconds the first time). It's the right tool for true end-to-end / wiring tests, but wasteful when you only need one slice (a controller, or JPA). The fix is context caching and/or narrower slice annotations like @WebMvcTest or @DataJpaTest.

code

java · 19 lines
java
// Loads the ENTIRE application context (all beans + auto-config).
// webEnvironment defaults to MOCK -> no real server/port.
@SpringBootTest
class OrderApplicationIT {

    @Autowired
    OrderService orderService; // whole graph is wired up to get this one bean

    @Test
    void placesOrder() {
        assertThat(orderService.place(sampleOrder())).isNotNull();
    }
}

// Starts a REAL embedded server on a random port (heavier still):
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class OrderEndToEndIT {
    @Autowired TestRestTemplate rest;
}

go deeper

for a junior

Must know it loads the full context and is therefore slow, and that slices exist as a lighter alternative.

for a middle

Should articulate the startup steps (scan, auto-config, wiring) and that the default webEnvironment is MOCK.

for a senior

Frames cost as context-build cost and connects it to caching and slice choice as mitigations.

for a principal

Reasons about suite-wide startup budget, context variation proliferation, and CI wall-clock as an architectural concern.

## What @SpringBootTest does `@SpringBootTest` is Spring Boot's integration-test annotation. When a test class is annotated with it, Spring builds a real **`ApplicationContext`** — the same container of beans that runs in production — before the tests execute. Building that context is expensive because it does the full startup sequence: 1. **Configuration discovery** — it finds your `@SpringBootApplication` class (or an explicit `classes = ...`) and reads its `@SpringBootConfiguration` + `@EnableAutoConfiguration` + `@ComponentScan`. 2. **Component scanning** — it scans your packages via reflection and registers every `@Component`, `@Service`, `@Repository`, `@Controller`, `@Configuration` as a bean definition. 3. **Auto-configuration** — `@EnableAutoConfiguration` evaluates *all* auto-config classes on the classpath (hundreds of them). Each is guarded by `@ConditionalOn...` checks. Matching ones create beans: a `DataSource` + Hikari pool, an `EntityManagerFactory` (Hibernate), Jackson `ObjectMapper`, Spring Security filter chain, Micrometer, etc. 4. **Bean instantiation & wiring** — every singleton bean is constructed and dependency-injected, `@PostConstruct` runs, `@Bean` methods execute. 5. **Embedded server (optional)** — controlled by `webEnvironment`: - `MOCK` (default) — a mock servlet environment, **no** real port/server. - `RANDOM_PORT` / `DEFINED_PORT` — starts a **real embedded Tomcat/Jetty/Netty** listening on a port. - `NONE` — no web environment at all. All of that for what might be a single assertion. First-time cost is commonly **2–10+ seconds**; multiply across dozens of test classes and the suite crawls. ## Why it matters The cost is not the *test body*, it's **context construction**. The mitigation strategies all attack that: - **Context caching** — Spring's `TestContext` framework caches contexts by configuration key and reuses them across test classes, so you pay the build cost once per *unique* configuration, not per class. - **Slices** — `@WebMvcTest`, `@DataJpaTest`, `@JsonTest`, `@WebFluxTest`, `@RestClientTest` etc. build a *minimal* context with only the beans that layer needs, skipping the rest of the app and auto-config. - **`@MockBean` / mocks** — replace heavy collaborators so you don't wire the whole graph. ## When to still use @SpringBootTest - True end-to-end tests that verify the whole app wires up. - Cross-cutting concerns spanning multiple layers. - Tests hitting a real embedded server via `TestRestTemplate` / `WebTestClient` with `RANDOM_PORT`. ## Gotchas - Each distinct `@SpringBootTest` configuration (different `@MockBean` sets, `@TestPropertySource`, active profiles, `webEnvironment`, `@DirtiesContext`) is a **separate cached context** — proliferation of variations kills caching and re-slows the suite. - Default `webEnvironment = MOCK` does **not** start a server; a common misconception is that `@SpringBootTest` always binds a port.

  • Does @SpringBootTest start an embedded web server by default?
    No. The default is webEnvironment = MOCK, which provides a mock servlet environment with no real port. You only get a real embedded Tomcat/Netty when you set RANDOM_PORT or DEFINED_PORT.
  • If the context build is the cost, why doesn't the second @SpringBootTest class pay it again?
    Because Spring's TestContext framework caches the context and reuses it for any test class whose configuration key matches — so identical configurations share one built context.

saying these in an interview costs you the question

  • Thinking @SpringBootTest always starts a real server on port 8080 (default is MOCK, no server).
  • Believing the slowness comes from the test logic rather than from building the ApplicationContext.
  • Assuming a fresh context is created for every test method.

context

open as a page

Instead of @SpringBootTest, which slice would you pick for a controller vs a repository, and what does each load?

level: middleimportance: must knowfreq 68%

basics

~10 s

Use @WebMvcTest for a controller — it loads only the web/MVC layer and mocks the rest. Use @DataJpaTest for a repository — it loads JPA/Hibernate plus an in-memory (or configured) database, nothing else.

open as a page

How does Spring's TestContext caching work, and what causes it to build a new context instead of reusing one?

level: seniorimportance: must knowfreq 60%

basics

~20 s

Spring caches each ApplicationContext keyed by its configuration. Tests with an identical configuration reuse the cached context; any difference in the key (classes, profiles, properties, @MockBean set, web environment) triggers building a new, separately cached context.

open as a page

What is @MockBean, and what is the hidden performance trade-off of using it heavily across @SpringBootTest classes?

level: seniorimportance: should knowfreq 45%

basics

~20 s

@MockBean adds (or replaces) a bean in the test context with a Mockito mock. The hidden cost: each distinct set of @MockBean declarations is part of the context cache key, so heavy/varied use forks many separate contexts and slows the suite.

open as a page

You're the tech lead on a service whose test suite takes 12 minutes, dominated by dozens of @SpringBootTest classes. How do you diagnose and cut the cost without losing coverage?

level: principalimportance: should knowfreq 38%

basics

~20 s

Measure how many distinct contexts are being built, convert most @SpringBootTest classes to slices or a shared base config, eliminate needless @DirtiesContext and per-class property/mock variations so tests reuse cached contexts, and reserve full @SpringBootTest for a few genuine end-to-end tests.

open as a page