skip to content

What is @ExtendWith(SpringExtension.class), and what does adding it to a JUnit 5 test class actually do?

level: juniorimportance: must knowfreq 70%

answer

  1. JUnit 5 extension = bridge to TestContext
  2. Replaces @RunWith(SpringRunner)
  3. Meta-annotated inside @SpringBootTest etc.
  4. No extension => @Autowired is null
  5. Holds TestContextManager

basics

~10 s

SpringExtension is the JUnit 5 hook that plugs Spring's TestContext into your test. Adding @ExtendWith(SpringExtension.class) lets Spring build an ApplicationContext and inject beans (e.g. via @Autowired) into the test.

solid answer

~30 s

@ExtendWith(SpringExtension.class) registers Spring's SpringExtension with JUnit Jupiter (JUnit 5). It is the integration point between JUnit 5's extension model and Spring's TestContext Framework. On it, Spring creates and caches an ApplicationContext for the test class, injects beans into fields/constructor/method parameters, and drives Spring test lifecycle (dependency injection, @DirtiesContext, transactional test support, etc.). Without it, annotations like @Autowired, @ContextConfiguration, or @MockBean on a plain JUnit 5 test do nothing because nobody loads the context. In practice you rarely write @ExtendWith(SpringExtension.class) directly — meta-annotations like @SpringBootTest, @SpringJUnitConfig, @WebMvcTest, or @DataJpaTest already include it. It replaces the JUnit 4 @RunWith(SpringRunner.class)/SpringJUnit4ClassRunner.

code

java · 20 lines
java
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.assertj.core.api.Assertions.assertThat;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = AppConfig.class)
class GreetingServiceTest {

    @Autowired
    GreetingService greetingService; // null without SpringExtension

    @Test
    void injectsBean() {
        assertThat(greetingService).isNotNull();
        assertThat(greetingService.greet("Ada")).isEqualTo("Hello, Ada");
    }
}

go deeper

for a junior

Know it's the annotation that makes @Autowired work in a Spring test and that it replaces @RunWith(SpringRunner).

for a middle

Know it wires the TestContext Framework in and that @SpringBootTest/@SpringJUnitConfig include it via meta-annotation.

for a senior

Explain which JUnit 5 callback interfaces it implements and how it delegates to TestContextManager/TestExecutionListeners.

for a principal

Discuss context caching, extension composability vs the single-Runner limit, and when raw @ExtendWith is preferable to composed annotations.

## The problem it solves JUnit 5 (the 'JUnit Jupiter' programming model) does not know anything about Spring. To make Spring participate in a test's lifecycle, JUnit 5 exposes an **extension model**: you register an `Extension` and JUnit calls it back at well-defined points (before all tests, before each test, when resolving a method parameter, etc.). **`SpringExtension`** (full name `org.springframework.test.context.junit.jupiter.SpringExtension`) is Spring's implementation of several of those JUnit 5 callback interfaces. Registering it with `@ExtendWith(SpringExtension.class)` is what connects JUnit 5 to the **Spring TestContext Framework (TCF)** — the engine that has, since Spring 2.5, been responsible for loading and caching an `ApplicationContext` for tests. ## What it actually does `SpringExtension` implements JUnit 5 interfaces including: - `BeforeAllCallback` / `AfterAllCallback` — class-level setup/teardown of the TestContext. - `BeforeEachCallback` / `AfterEachCallback` — prepares the test instance before each method (dependency injection, transaction start, etc.). - `TestInstancePostProcessor` — performs **field** and **constructor** dependency injection into the test instance. - `ParameterResolver` — resolves **method/constructor parameters** as Spring beans (parameter injection). Internally it holds a `TestContextManager`, which manages the `TestContext` and the chain of `TestExecutionListener`s (e.g. `DependencyInjectionTestExecutionListener`, `DirtiesContextTestExecutionListener`, `TransactionalTestExecutionListener`). ## Without it On a plain JUnit 5 test with no `SpringExtension`, annotations such as `@Autowired`, `@ContextConfiguration`, `@MockBean`, `@Transactional` are simply ignored — no context is loaded and no injection happens. Your `@Autowired` fields stay `null` and you get a `NullPointerException`. ## You rarely write it directly Most Spring test annotations are **meta-annotated** with `@ExtendWith(SpringExtension.class)`, so it is pulled in transitively: - `@SpringBootTest` - `@SpringJUnitConfig` / `@SpringJUnitWebConfig` - Slice tests: `@WebMvcTest`, `@DataJpaTest`, `@JsonTest`, `@RestClientTest`, etc. You only add `@ExtendWith(SpringExtension.class)` by hand when you compose your own configuration with raw `@ContextConfiguration` and don't want a pre-built composed annotation. ## Relationship to JUnit 4 In JUnit 4 you wrote `@RunWith(SpringJUnit4ClassRunner.class)` (aliased as `@RunWith(SpringRunner.class)`). JUnit 5 replaced the single 'Runner' with the composable extension model, so the Runner became the `SpringExtension`. A JUnit 5 test can have many extensions; a JUnit 4 test could have only one Runner — this is the key architectural improvement. ## Gotchas - Mixing JUnit 4 imports (`org.junit.Test`) with JUnit 5 will silently not run under the Jupiter engine — make sure you import `org.junit.jupiter.api.Test`. - `@ExtendWith` is repeatable/composable, so `SpringExtension` coexists with, say, `MockitoExtension`. - The context is **cached** per unique configuration across the whole test suite; `SpringExtension` participates in that caching — it does not build a fresh context per test unless configuration differs or `@DirtiesContext` is used.

  • What is the JUnit 4 equivalent of @ExtendWith(SpringExtension.class)?
    @RunWith(SpringJUnit4ClassRunner.class), commonly written as @RunWith(SpringRunner.class). JUnit 5 replaced the single Runner with the composable extension model, so the Runner became SpringExtension.
  • If your test has @ContextConfiguration but no SpringExtension, what happens?
    Nothing loads the context, so @Autowired fields stay null and you typically get a NullPointerException. @ContextConfiguration alone is inert without the extension (or a meta-annotation that includes it) to drive the TestContextManager.

saying these in an interview costs you the question

  • Thinking @Autowired works in a JUnit 5 test with no extension or Spring test annotation
  • Confusing SpringExtension with @SpringBootTest (the latter includes the former, not vice versa)
  • Saying JUnit 5 still uses @RunWith/SpringRunner

context