skip to content

In JUnit 5 (Jupiter), how do you plug a reusable extension such as the Mockito or Spring test integration into a test, and which declaration sites are valid for the @ExtendWith annotation?

level: juniorimportance: must knowfreq 60%

answer

  1. @ExtendWith = declarative, class/method/field/parameter/meta-annotation
  2. No-arg constructor → cannot configure
  3. Inherited by subclasses and @Nested
  4. Repeatable + array; duplicates deduped
  5. Stackable, unlike JUnit 4 @RunWith

basics

~20 s

Declaratively, with @ExtendWith(SomeExtension.class). It can go on a test class, on a single test method, on a field or parameter, or on your own meta-annotation. JUnit builds the extension with its no-arg constructor, and class-level registration is inherited by subclasses and @Nested classes.

solid answer

~50 s

JUnit 5 replaced JUnit 4's runners and rules with one **Extension** API, and the everyday way to register one is declarative `@ExtendWith`. - On a **class**: `@ExtendWith(MockitoExtension.class)` applies to every test in it, and is inherited by subclasses and `@Nested` classes. - On a **test method**: applies to that method only. - On a **field or parameter** (Jupiter 5.8+): useful for a `ParameterResolver` you want for one argument. - It is repeatable and takes an array: `@ExtendWith({A.class, B.class})`. - It composes: put `@ExtendWith` on your own annotation and you have a `@SpringBootTest`-style meta-annotation. Unlike JUnit 4's `@RunWith`, you can stack as many extensions as you want. The limitation is that Jupiter instantiates the extension through its **no-arg constructor**, so you cannot configure the instance. When the extension needs constructor arguments or a builder, switch to programmatic registration with `@RegisterExtension` on a field. Declaring the same extension type twice in one context is harmless — Jupiter registers each implementation once.

code

java · 15 lines
java
@ExtendWith(MockitoExtension.class)
class OrderServiceTest {

    @Test
    @ExtendWith(TimingExtension.class)
    void slowPathIsMeasured() { }
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith({MockitoExtension.class, TimingExtension.class})
@interface UnitTest { }

@UnitTest
class PaymentServiceTest { }

go deeper

for a junior

Know that @ExtendWith(X.class) on the class is how you turn on Mockito or Spring in a JUnit 5 test, and that you can list several.

for a middle

Add the other targets (method, field/parameter, meta-annotation), inheritance to subclasses and @Nested, and the no-arg-constructor limitation that pushes you to @RegisterExtension.

for a senior

Explain the registry model — deduplication, registration order, before/after callbacks nesting like try/finally — and design shared meta-annotations for a codebase's test archetypes.

for a principal

Frame it as the team's testing contract: a small set of curated composed annotations (@UnitTest, @IntegrationTest) so test setup is uniform and swappable, rather than every class hand-rolling extension lists.

## One plug-in mechanism instead of two JUnit 4 had two extension points that did not compose. `@RunWith(SomeRunner.class)` was powerful but exclusive: one runner per class, so you could not have the Spring runner and the Mockito runner at the same time. `@Rule`/`@ClassRule` composed but could only wrap statement execution. Jupiter merges both ideas into the `org.junit.jupiter.api.extension.Extension` marker interface, which sub-interfaces such as `BeforeEachCallback`, `ParameterResolver`, `TestInstancePostProcessor` and `ExecutionCondition` extend. An extension is just a class implementing one or more of those callback interfaces; registering it means telling Jupiter to add it to the extension registry for some scope. ## Declarative registration with @ExtendWith `@ExtendWith` is the declarative path — you name a **class**, Jupiter creates the instance for you. Valid targets: 1. **Test class** — the extension participates in every test in that class. Because Jupiter honours annotation inheritance, an abstract `IntegrationTestBase` annotated with `@ExtendWith` passes it to every subclass, and `@Nested` inner classes inherit from the enclosing class. 2. **Test method** — scoped to that one method; class-level callbacks such as `BeforeAllCallback` cannot fire meaningfully there because the class-level phase has already happened. 3. **Field or parameter** (since Jupiter 5.8) — mostly for `ParameterResolver`s that should only apply to one injection point. 4. **Another annotation** (meta-annotation) — this is how `@SpringBootTest` and `@DataJpaTest` work: they are ordinary annotations that themselves carry `@ExtendWith(SpringExtension.class)`, so the user never types `@ExtendWith`. `@ExtendWith` is `@Repeatable` and its value is an array, so `@ExtendWith({A.class, B.class})` and two stacked annotations are equivalent. ## Instantiation and deduplication Jupiter instantiates a declaratively registered extension via its **default (no-arg) constructor**. That is the whole reason `@RegisterExtension` exists: with `@ExtendWith` there is no place to pass a port number, a builder result, or a preconfigured client. The extension class must be concrete, and it must not be `private`. Jupiter also **deduplicates**: the same extension implementation registered more than once inside one registry hierarchy is only applied once. So a base class with `@ExtendWith(SpringExtension.class)` plus a subclass repeating it does not double the callbacks. This is why meta-annotations can be layered freely. ## Ordering, briefly Extensions are registered in a deterministic order: inherited/class-level declarations first (outermost class down), then the order in which `@ExtendWith` annotations appear, and programmatically registered extensions after them. "Before" callbacks then run in registration order and "after" callbacks in reverse — an onion, like nested try/finally blocks. Relying on the exact interleaving of unrelated extensions is fragile; if order matters, use `@Order` on programmatically registered fields. ## Choosing between the two styles Use `@ExtendWith` when the extension needs no configuration — Mockito, Spring, your own logging or clock-freezing extension with sensible defaults. Use `@RegisterExtension` when you must build the instance, or when the test body needs to talk to it (for example asking a WireMock extension for its randomly assigned port). Both end up in the same registry; the annotation only decides who constructs the object.

  • Why can you stack several extensions in JUnit 5 when JUnit 4 allowed only one @RunWith?
    A JUnit 4 runner owned the whole execution lifecycle of the class, so two runners would each want to drive the run and could not be combined. Jupiter inverts that: the engine drives execution and extensions only implement narrow callback interfaces the engine invokes at defined points. Because callbacks compose (they nest like try/finally), any number can be registered.
  • What happens if the same extension class is registered on both a base class and its subclass?
    Nothing bad — Jupiter deduplicates extension implementations within a registry hierarchy, so it is applied once. This is what makes layered meta-annotations safe: @SpringBootTest on a subclass of a base class that already pulls in SpringExtension does not run Spring's callbacks twice.
  • How would you write a reusable annotation that pulls in several extensions plus a tag?
    Create your own annotation with @Retention(RUNTIME) and the right @Target, then annotate it with @ExtendWith({...}) and @Tag("integration"). Jupiter resolves meta-annotations recursively, so applying your annotation to a test class registers everything it carries. This is exactly the mechanism behind @SpringBootTest and @DataJpaTest.

@ExtendWith is like listing plugin class names in a config file: the framework news up each one for you with defaults. @RegisterExtension is handing the framework an object you built yourself.

saying these in an interview costs you the question

  • Claiming you can only have one extension per class, confusing it with JUnit 4's @RunWith
  • Thinking @ExtendWith lets you pass constructor arguments or configuration to the extension
  • Believing extensions are not inherited, so every subclass must repeat the annotation
  • Saying JUnit 5 still supports @Rule natively (it does not; only the junit-jupiter-migrationsupport module offers limited rule support)

context