JUnit 5's extension model includes TestInstancePostProcessor. What is it for, when in the lifecycle does it run, and how does the per-class test instance lifecycle change its behaviour?
answer
- postProcessTestInstance(instance, context) — right after construction
- before BeforeEachCallback and @BeforeEach
- fields only; parameters need ParameterResolver
- @Mock / Spring autowiring are the canonical users
- PER_CLASS ⇒ runs once, injected state shared
basics
~20 sTestInstancePostProcessor lets an extension act on a freshly created test instance — typically injecting into annotated fields. It runs right after instantiation, before BeforeEachCallback. With the default per-method lifecycle it runs for every test; with per-class lifecycle it runs once.
solid answer
~50 s`TestInstancePostProcessor` has one method, `postProcessTestInstance(Object testInstance, ExtensionContext context)`, called immediately after JUnit creates the test class instance and before any `BeforeEachCallback` or `@BeforeEach`. Its job is to act on the object itself — almost always **field injection**: scanning declared fields for an annotation and setting them reflectively. `MockitoExtension` initialising `@Mock` fields and Spring's `SpringExtension` performing dependency injection into the test instance are the canonical examples. Lifecycle matters. With the default `Lifecycle.PER_METHOD`, JUnit creates a new instance for each test method, so the post-processor runs **once per test** and each test gets fresh fields. With `@TestInstance(Lifecycle.PER_CLASS)`, one instance serves the whole class, so it runs **once** — injected mocks or fixtures are then shared and mutated state leaks between tests unless you reset them in `BeforeEachCallback`. Remember it only reaches fields. Parameters of test or lifecycle methods are supplied by `ParameterResolver`; many extensions implement both so either style works.
code
java · 15 linesclass FixedClockExtension implements TestInstancePostProcessor {
@Override
public void postProcessTestInstance(Object instance, ExtensionContext ctx) throws Exception {
for (Field f : instance.getClass().getDeclaredFields()) {
if (f.isAnnotationPresent(FixedClock.class)) {
if (Modifier.isFinal(f.getModifiers())) {
throw new ExtensionConfigurationException(
"@FixedClock field must not be final: " + f.getName());
}
f.setAccessible(true);
f.set(instance, Clock.fixed(Instant.parse("2020-01-01T00:00:00Z"), ZoneOffset.UTC));
}
}
}
}go deeper
Know that it is the hook that fills in annotated fields on the test object right after it is constructed, before any setup method runs.
Add that it cannot touch method parameters (ParameterResolver does) and that the default per-method lifecycle means one run per test.
Discuss the PER_CLASS interaction and state leakage, resetting in BeforeEachCallback, failing fast on misconfigured fields, and inherited or nested-class fields.
Frame it as an injection contract for the test tier: what the extension owns, how it degrades under alternative lifecycles, and how it composes with DI-driven instance factories.
## The interface ```java public interface TestInstancePostProcessor extends Extension { void postProcessTestInstance(Object testInstance, ExtensionContext context) throws Exception; } ``` JUnit hands you the just-constructed test class instance. You may do anything with it; in practice extensions use reflection to populate fields. ## Where it sits in the lifecycle For the default per-method instance lifecycle, the sequence around one test is: 1. `BeforeAllCallback`, then `@BeforeAll` (container level, no instance yet for per-method lifecycle) 2. the test class instance is created (optionally by a `TestInstanceFactory`) 3. **`TestInstancePostProcessor`** runs 4. `BeforeEachCallback` 5. `@BeforeEach` 6. `BeforeTestExecutionCallback`, the test body, and the unwinding After* chain So the post-processor is the earliest point at which an extension can touch the object, and it is guaranteed to have run before any user setup — a test's `@BeforeEach` can safely use an injected field. When several extensions implement it, they run in registration order, like other before-style callbacks. ## What it is used for **Field injection.** The dominant use. Walk `testInstance.getClass().getDeclaredFields()` (including inherited fields), find those carrying your annotation, build or look up the value, `setAccessible(true)`, and assign. Real-world examples: - Mockito's extension creating mocks and spies for `@Mock`/`@Spy` fields and wiring `@InjectMocks`. - Spring's `SpringExtension` performing autowiring into the test instance from the test `ApplicationContext`. - Home-grown extensions injecting a stub client, a fixed `Clock`, a tenant id or a generated test-data builder. **Registering the instance somewhere.** For example putting a reference into the `ExtensionContext` store so later callbacks (or an exception handler) can read fields off it, or registering the instance with an event bus that the code under test publishes to. **Validation.** Failing fast with a clear message when the test class is mis-declared — a required field is missing, an annotation is on a `final` or `static` field, two mutually exclusive annotations are present. Throwing here produces a failure before any test body runs, which is much clearer than a `NullPointerException` inside a test. ## Fields versus parameters `TestInstancePostProcessor` can only reach **fields of the test instance**. It cannot supply arguments to a test method, a constructor or a lifecycle method — that is `ParameterResolver`'s job. Many extensions implement both so users can write either ```java @Mock Repository repo; // field injection void test(@Mock Repository repo) // parameter resolution ``` It also cannot inject into `static` fields for a per-method lifecycle class in any meaningful shared way; class-level resources belong in `BeforeAllCallback` plus the store. ## The per-class lifecycle interaction By default (`Lifecycle.PER_METHOD`) JUnit constructs a new test instance for every test method. Consequences: the post-processor runs once per test, injection is repeated, and each test starts with fresh field values — the isolation most people assume. Annotating the class `@TestInstance(Lifecycle.PER_CLASS)` — commonly done so `@BeforeAll` can be non-static or so a `@TestFactory`/`@MethodSource` can be an instance method — creates **one instance for the whole class**. Then: - the post-processor runs **once**, before the first test; - injected objects are shared by every test in the class; - any mutation a test makes to an injected field (stubbings recorded on a mock, entries added to a collection, a mutated builder) is visible to subsequent tests, making them order-dependent; - an extension that assumed "one injection per test" silently changes behaviour when a user adds that annotation. A robust extension therefore either resets shared state in `BeforeEachCallback` (for mocks: re-create or reset them per test), or explicitly detects the lifecycle via the `ExtensionContext`'s test-instance lifecycle accessor and adapts — or documents that it requires the per-method lifecycle. ## Related hooks worth knowing - `TestInstanceFactory` — replaces instance *creation* entirely (used when a DI container must construct the test class). At most one may be registered per class. - `TestInstancePreDestroyCallback` — the symmetric hook to clean up an instance before it is discarded, useful for closing injected resources. - `TestInstances` on the `ExtensionContext` — for `@Nested` classes, gives access to the enclosing instances as well as the innermost one, which matters when injecting into nested hierarchies. ## Practical cautions Reflection into a test class is inherently coupling: prefer a narrow, annotation-driven contract and fail loudly when the annotation is used on something you cannot handle. Remember inherited and `@Nested`-enclosing fields exist. And keep the post-processor cheap — with the per-method lifecycle it runs for every single test in the suite.
- An extension injects a mock via TestInstancePostProcessor. A user adds @TestInstance(Lifecycle.PER_CLASS) to the class and tests start interfering with each other. Why, and what should the extension do?With the per-class lifecycle a single test instance serves every method, so the post-processor runs once and the injected mock is shared; stubbings and recorded invocations from one test leak into the next, making results order-dependent. The extension should reset or re-create the injected state in BeforeEachCallback, or detect the lifecycle from the ExtensionContext and either adapt or fail with a clear configuration error.
- Why do many extensions implement both TestInstancePostProcessor and ParameterResolver?The post-processor can only populate fields on the test instance, so method parameters would remain unresolved; ParameterResolver covers arguments to constructors, lifecycle methods and test methods. Supporting both lets users choose field injection for values shared across a class's tests and parameter injection for values scoped to a single test, without the extension caring which style they picked.
saying these in an interview costs you the question
- Thinking TestInstancePostProcessor can inject test method parameters
- Believing it always runs once per class regardless of the instance lifecycle
- Assuming it runs after @BeforeEach rather than before it
- Injecting mutable shared state without resetting it per test under PER_CLASS