skip to content

Do @BeforeEach and @AfterEach run around each case produced by a JUnit 5 @TestFactory method? Explain the lifecycle and how you would get per-case setup and teardown.

level: seniorimportance: must knowfreq 30%

answer

  1. callbacks wrap the factory, not the cases
  2. one instance, one fixture, N cases
  3. extension @BeforeEach/@AfterEach callbacks also skipped
  4. per-case setup goes inside the executable
  5. order-dependent failures = the tell

basics

~20 s

No. @BeforeEach and @AfterEach wrap the factory method itself — once — not each generated case. Test-level extension callbacks are likewise not invoked per case. Per-case setup must be written inside each generated case's own executable, or wrapped by a helper you apply when building them.

solid answer

~60 s

They run **once, around the factory method**, exactly as they would around a `@Test` method. The generated cases execute *after* the factory has returned, and Jupiter does **not** re-run `@BeforeEach`/`@AfterEach` for each of them. The same holds for the corresponding extension callbacks (`BeforeEachCallback`/`AfterEachCallback`) and test-level extension points — dynamic cases are not full test methods, so most method-level extension machinery does not fire per case. The consequence is that generated cases **share the state** the factory method's fixture created. If one case mutates that state, later cases see it, which reintroduces order dependence that per-method isolation normally removes. To get per-case setup you build it into the executable you hand to each node: create the fixture inside the lambda, or wrap the body in a helper that does setup/teardown around it (a `try/finally`, or a small function that returns the wrapped `Executable`). Alternatively, if per-case isolation is what you need, a statically declared test — a parameterized test or ordinary `@Test` methods — is usually the better fit, because those get the full lifecycle.

code

java · 23 lines
java
class DynamicLifecycleTest {

    private List<String> shared;

    @BeforeEach
    void setUp() {
        shared = new ArrayList<>();   // runs ONCE for the whole factory
    }

    @TestFactory
    Stream<DynamicTest> cases() {
        return Stream.of("a", "b", "c")
                .map(input -> DynamicTest.dynamicTest("handles " + input, () -> {
                    Service service = new Service();      // per-case setup
                    try {
                        shared.add(input);                // shared across cases!
                        assertTrue(service.handles(input));
                    } finally {
                        service.close();                  // per-case teardown
                    }
                }));
    }
}

go deeper

for a junior

Know the headline: the setup method runs once for the whole factory, not before each generated case.

for a middle

State the full ordering, note that all cases share one instance and fixture, and show per-case setup inside the executable.

for a senior

Explain why (dynamic cases are executables, not methods), the resulting order-dependence hazard and its symptoms, and when to switch to statically declared tests instead.

for a principal

Frame it as a suite policy: where data-driven generation is worth the loss of isolation and tooling fidelity, and what conventions keep generated cases independent.

## The actual execution order For a class containing a `@TestFactory` method: 1. `@BeforeAll` 2. `@BeforeEach` — **once** 3. the factory method is invoked and returns nodes 4. every generated case runs, in order, with **no** lifecycle callbacks between them 5. `@AfterEach` — **once** 6. `@AfterAll` So the whole set of generated cases sits *inside* one `@BeforeEach`/`@AfterEach` pair, in the same test instance. There is one test-class instance for the factory (under the default per-method lifecycle), and every generated case runs against that same instance. Note that with a lazily consumed `Stream`, steps 3 and 4 interleave — a node is produced, executed, then the next is pulled — but that does not change the lifecycle: no callbacks fire between cases either way. ## Why it works this way Dynamic cases are not methods. Jupiter's method-level lifecycle is defined in terms of test *methods* it discovered and the instance it created for them. A dynamic case is a display name plus an `Executable` created at runtime; there is no method to reflect on, no parameters to resolve, no annotations to inspect. Rather than invent a half-working lifecycle, JUnit documents plainly that lifecycle callbacks and the corresponding extension callbacks are **not** executed for dynamic tests. This also explains other absences people trip over: per-case timeouts from `@Timeout` on the factory apply to the factory method as a whole; extensions that intercept test method invocation do not intercept dynamic case bodies. ## What this means for state Shared state across generated cases is the practical hazard. A factory that builds a service in `@BeforeEach` and generates twenty cases against it gives all twenty cases one service instance. If case 3 leaves it dirty, case 17 may fail — and it will fail *only* when the whole factory runs, never when you reproduce it alone (which you cannot do easily anyway, since dynamic cases are not individually selectable before the run). Symptoms to recognise in an interview answer: order-dependent failures inside one factory, failures that vanish when the input list is filtered, a case that passes in isolation but fails in sequence. ## Getting per-case setup and teardown **1. Build it into the executable.** The cleanest approach — each case creates and disposes of what it needs: ```java @TestFactory Stream<DynamicTest> perCaseFixture() { return inputs.stream().map(input -> DynamicTest.dynamicTest( "case " + input, () -> { Service service = new Service(); // per-case setup try { assertTrue(service.handles(input)); } finally { service.close(); // per-case teardown } })); } ``` **2. Wrap with a helper.** Factor the try/finally into a function that takes the body and returns a wrapped `Executable`, so every case gets the same treatment and the intent is explicit. **3. Reset between cases.** If the shared fixture is expensive to rebuild, reset it at the *start* of each case's body rather than the end, so a case that throws does not poison the next. **4. Reconsider the tool.** If every case genuinely needs full isolation and lifecycle, dynamic tests may be the wrong mechanism. Statically declared tests keep per-method instances, the full lifecycle, extension support, and individual selectability. Choose dynamic generation when the *set of cases* must come from runtime data, not merely to loop over a known list. ## Interaction with @Nested A `@TestFactory` inside a `@Nested` class behaves the same way: the outer and nested `@BeforeEach` both run once, outside-in, around the factory invocation — not around each generated case. ## The sentence to say out loud "Lifecycle callbacks wrap the factory method, not the generated cases; dynamic cases share one instance and one fixture, so per-case setup has to live inside each case's executable — and if I need real isolation per case, that is a signal to use statically declared tests instead."

  • A generated case passes when the factory's input list has one entry but fails with the full list. What is the likely cause?
    Shared mutable state between generated cases. All cases run inside one @BeforeEach/@AfterEach pair against a single test-class instance, so an earlier case has left the fixture in a state the later case does not expect. The fix is to construct or reset the fixture inside each case's own executable, or to move to statically declared tests that get per-method isolation.
  • Does @Timeout on a @TestFactory method apply to each generated case?
    No — it applies to the factory method as a whole, which in practice means the total time to produce and run all generated cases. There is no per-case timeout from that annotation because dynamic cases are not test methods. If you need a per-case bound, enforce it inside each case's executable, for example with assertTimeout around the body.
  • Do extensions registered on the class still work with a @TestFactory?
    Class-level and container-level extension points still apply around the factory method, and parameter resolution works for the factory method's own parameters. What does not happen is per-case invocation of test-method-level callbacks such as BeforeEachCallback and AfterEachCallback for each generated case, because those cases are executables rather than discovered methods.

saying these in an interview costs you the question

  • Saying @BeforeEach runs before every generated case
  • Assuming each generated case gets its own test-class instance
  • Expecting extension callbacks such as BeforeEachCallback to fire per dynamic case
  • Treating order-dependent failures inside a factory as flakiness rather than shared state
  • Believing @Timeout on the factory bounds each generated case individually

context