How do lifecycle methods like @BeforeEach and @AfterEach stack and execute across nesting levels in JUnit 5?
answer
- @BeforeEach: outer → inner (descend)
- @AfterEach: inner → outer (ascend, mirror)
- Wraps test like nested try/finally
- @BeforeAll static ⇒ usually top-level only
- PER_CLASS lets @Nested host non-static @BeforeAll
basics
~20 sOuter @BeforeEach methods run before inner ones, going from the outermost class inward, then the test runs, then @AfterEach methods run in reverse order — innermost first, outermost last. Each level layers its own setup and teardown around the test.
solid answer
~40 sLifecycle callbacks stack by nesting depth. For a test in a @Nested class, JUnit runs every enclosing class's @BeforeEach from the outermost in toward the test, then the test method, then every @AfterEach from the innermost out — teardown mirrors setup in reverse. This lets each level contribute its own arrange/cleanup: the outer class establishes base context, each nested level refines it. @BeforeAll/@AfterAll are class-level and run once around the whole class; by default they must be static, so they typically live on the top-level class — a @Nested class can't host a static @BeforeAll unless you switch that class to @TestInstance(PER_CLASS), which allows a non-static @BeforeAll. The key mental model: @BeforeEach is a stack pushed outer-to-inner and @AfterEach is popped inner-to-outer, wrapping the test in layered fixtures.
code
java · 14 linesclass FilesystemTest {
@BeforeEach void mountRoot() { System.out.println("1 outer-before"); }
@AfterEach void unmount() { System.out.println("4 outer-after"); }
@Nested
class WhenDirectoryExists {
@BeforeEach void createDir() { System.out.println("2 inner-before"); }
@AfterEach void deleteDir() { System.out.println("3 inner-after"); }
@Test
void canWriteFile() { System.out.println("--- test ---"); }
}
}
// 1 outer-before, 2 inner-before, --- test ---, 3 inner-after, 4 outer-aftergo deeper
Knows @BeforeEach runs before each test and @AfterEach after, and that outer setup runs before inner.
States that @BeforeEach descends outer→inner and @AfterEach ascends inner→outer across nesting levels.
Explains the full stacking model including @BeforeAll's static constraint, the PER_CLASS exception, and designs layered fixtures that unwind correctly.
Establishes patterns for layered test fixtures and resource lifecycles in deeply nested suites, ensuring deterministic acquire/release ordering and avoiding teardown-order hazards.
## Lifecycle callbacks recap JUnit 5 has four main **lifecycle annotations** — methods the framework calls automatically around your tests: - **`@BeforeEach`** — runs *before each* `@Test` method (per-test setup / arrange). - **`@AfterEach`** — runs *after each* test (per-test cleanup / teardown). - **`@BeforeAll`** — runs *once before all* tests in a class. - **`@AfterAll`** — runs *once after all* tests in a class. ## How they stack across nesting When a test lives inside one or more `@Nested` classes, JUnit executes the *per-test* callbacks of **every enclosing level**, ordered by depth: 1. **`@BeforeEach` runs outermost → innermost.** The top-level class's `@BeforeEach` runs first, then each nested level's, working *inward* toward the test. This means general context is established before specific context. 2. The **`@Test`** method runs. 3. **`@AfterEach` runs innermost → outermost** — the reverse of setup. The closest level cleans up first, then each enclosing level, working *outward*. Teardown unwinds in mirror order, like nested `try/finally` blocks. Visualize it as wrapping layers around the test: ``` Outer @BeforeEach Inner @BeforeEach >>> @Test <<< Inner @AfterEach Outer @AfterEach ``` ## Worked example ```java class FilesystemTest { @BeforeEach void mountRoot() { log("1 outer-before"); } @AfterEach void unmountRoot() { log("4 outer-after"); } @Nested class WhenDirectoryExists { @BeforeEach void createDir() { log("2 inner-before"); } @AfterEach void deleteDir() { log("3 inner-after"); } @Test void canWriteFile() { log("--- test ---"); } } } ``` Output order: `1 outer-before` → `2 inner-before` → `--- test ---` → `3 inner-after` → `4 outer-after`. Setup descends; teardown ascends. ## @BeforeAll / @AfterAll and nesting `@BeforeAll`/`@AfterAll` are **class-scoped** (run once per class, not per test). By default JUnit's lifecycle is **`PER_METHOD`**, and in that mode these methods **must be `static`**. Java forbids static methods on a non-static inner class — and a `@Nested` class *is* non-static — so **a @Nested class normally cannot declare a static @BeforeAll**. Two consequences: - Put once-per-suite setup on the **top-level** class as a static `@BeforeAll`; it runs before any test (including nested ones). - If you genuinely need once-per-nested-group setup, annotate that nested class with **`@TestInstance(TestInstance.Lifecycle.PER_CLASS)`**. In `PER_CLASS` mode JUnit reuses one instance for the class, which *permits a non-static `@BeforeAll`/`@AfterAll`* there. (Be aware `PER_CLASS` also means state persists across that group's methods.) When present at multiple levels, outer `@BeforeAll` runs before the nested group's `@BeforeAll`, and outer `@AfterAll` runs after the nested group's `@AfterAll` — same outer-then-inner / inner-then-outer logic as the per-each callbacks, just once per class. ## Why this ordering matters The outer-to-inner setup / inner-to-outer teardown contract lets you build **layered fixtures**: the outer class establishes coarse shared state (e.g., a connection), each nested class refines it (e.g., begins a transaction for that scenario), and teardown unwinds cleanly in reverse so resources acquired last are released first. Misreading the order — e.g., assuming inner teardown runs *after* outer — leads to use-after-free style bugs in tests (cleaning up a resource the inner level still depends on).
- In what order do @AfterEach methods run across two nesting levels?Innermost first, then outermost — the reverse of @BeforeEach. Teardown unwinds the layers in mirror order, like nested try/finally.
- Can a @Nested class declare a static @BeforeAll by default?No. With the default PER_METHOD lifecycle @BeforeAll must be static, but Java forbids static methods on a non-static inner class. Switch that class to @TestInstance(PER_CLASS) to allow a non-static @BeforeAll, or put it on the top-level class.
saying these in an interview costs you the question
- Saying teardown runs in the same order as setup (it's reversed).
- Assuming inner @BeforeEach runs before outer @BeforeEach.
- Claiming a @Nested class can host a static @BeforeAll under the default lifecycle.