skip to content

@Nested Organization

Grouping related cases in non-static inner classes that share outer setup. Asked because good @Nested usage signals you structure tests around behavior, not around one class per method.

on this pageshow

questions

5

In JUnit 5, what does annotating an inner test class with @Nested do, and why must that class be non-static?

level: juniorimportance: must knowfreq 50%

answer

  1. @Nested = child container in the test tree
  2. non-static inner → implicit Outer.this reference
  3. outer.new Inner() per test method
  4. @BeforeEach outside-in, @AfterEach inside-out
  5. static nested = separate top-level container

basics

~20 s

@Nested makes an inner class a child test container: its tests run under the outer class and reuse the outer class's fields and @BeforeEach setup. It must be non-static so every nested instance is bound to a live enclosing instance.

solid answer

~50 s

`@Nested` marks a **non-static inner class** inside a test class as a child container in the test tree. Jupiter discovers it, and for each test inside it creates an instance of the outer class first, then the inner instance bound to it (`outer.new Inner()`). Because the inner instance carries an implicit reference to the enclosing instance, nested tests can read and extend fields the outer class set up — outer `@BeforeEach` runs before the nested one, so each nesting level adds a layer of fixture. It has to be non-static because that outer-instance link is exactly a Java *inner class* feature: a `static` nested class has no enclosing instance, so there would be nothing to inherit state from. That is also why a static nested class is treated as an independent test container rather than a child of the outer class. The practical payoff is a given/when/then shaped test tree: outer = subject, each `@Nested` = a scenario, methods = the assertions for that scenario.

code

java · 34 lines
java
class ShoppingCartTest {

    private Cart cart;

    @BeforeEach
    void createEmptyCart() {
        cart = new Cart();
    }

    @Test
    void newCartIsEmpty() {
        assertTrue(cart.isEmpty());
    }

    @Nested
    @DisplayName("when one item has been added")
    class WithOneItem {

        @BeforeEach
        void addItem() {
            cart.add(new Item("book", 10));
        }

        @Test
        void isNotEmpty() {
            assertFalse(cart.isEmpty());
        }

        @Test
        void totalEqualsItemPrice() {
            assertEquals(10, cart.total());
        }
    }
}

go deeper

for a junior

Be able to say @Nested groups related tests under the outer class, that the class is a non-static inner class, and that outer setup still applies.

for a middle

Add the mechanics: the outer.new Inner() instantiation chain, outside-in @BeforeEach / inside-out @AfterEach, and a fresh instance pair per test method under the default lifecycle.

for a senior

Explain why the non-static requirement is a consequence of Java inner-class semantics, what happens to a static nested class instead, and how the nesting shapes the reported test tree.

for a principal

Frame it as suite structure: when layered fixtures aid comprehension versus when a long nested file should be split by subject, and how nesting interacts with configuration inheritance and parallel execution.

## The problem @Nested solves A plain JUnit 5 test class is a flat list of `@Test` methods sharing one `@BeforeEach`. That works until the subject has several distinct states — an empty cart, a cart with one item, a cart already checked out. With a flat class you end up either repeating the same three setup lines in a dozen tests, or writing one `@BeforeEach` that sets up the union of every scenario and hoping each test only touches its own part. Both make tests hard to read and easy to break. `@Nested` (from `org.junit.jupiter.api`) lets you express those scenarios structurally. You declare an inner class per scenario, annotate it `@Nested`, and give it its own `@BeforeEach` that moves the subject into that state. The tests inside it then only assert. ## What Jupiter actually does During discovery, when Jupiter resolves a test class it looks at its member classes and treats every **non-static member class annotated with `@Nested`** as a nested test container — a child node in the test tree, with its own display name, its own lifecycle methods, and possibly its own `@Nested` children. During execution, with the default `PER_METHOD` test-instance lifecycle, running one test inside a nested class means Jupiter: 1. creates a fresh instance of the outermost class, 2. runs its `@BeforeEach` methods, 3. creates the inner instance bound to that outer instance (`outer.new Inner()` in Java terms), 4. runs the inner `@BeforeEach`, 5. runs the test method, 6. runs inner `@AfterEach`, then outer `@AfterEach`. So `@BeforeEach` callbacks run **outside-in** and `@AfterEach` **inside-out**, and every test method gets a brand-new outer+inner instance pair. Mutating outer state inside a nested test cannot leak into a sibling test. Class-level configuration on the enclosing class — registered extensions, `@TestInstance` lifecycle mode, `@DisplayNameGeneration` — is inherited by `@Nested` classes, so you configure once at the top. ## Why non-static is required In Java, a non-static member class (an *inner* class) instance holds an implicit reference to an instance of its enclosing class, reachable as `Outer.this`. That reference is what lets nested test code read the field the outer `@BeforeEach` just populated. A `static` nested class has no such reference; it is really a top-level class that happens to be namespaced inside another one. Jupiter's whole nesting model is built on that instance chain, so `@Nested` is defined for inner classes only. A `static` nested class is not a child container: it is discovered (by build-tool classpath scanning) as its own standalone test class named like `OuterTest$InnerTest`, with no outer fixture applied. Putting `@Nested` on a static class does not promote it — depending on the JUnit 5.x version you get silence, a logged warning, or a reported configuration problem, but never the nested behaviour you wanted. Two related declaration rules follow the same spirit: the class must not be `private`, and it must not be abstract or local/anonymous. Package-private (the default, no modifier) is the idiomatic choice. ## What you get from it - **Readable reports.** The IDE/report tree reads `ShoppingCartTest > when one item added > total equals item price`, which is close to a specification. - **Fixture layering.** Each level adds only its delta of setup; there is no giant shared `@BeforeEach`. - **Scoped helpers.** Helper methods and fields that only one scenario needs live in that scenario's class instead of polluting the whole file. - **Arbitrary depth.** `@Nested` classes can contain `@Nested` classes; in practice two or three levels is the readability ceiling. ## Common mistakes Declaring the class `static` out of habit (JUnit 4's `Enclosed` runner required static classes) is the classic error — the tests either silently stop running when you select the outer class in the IDE, or run without the outer fixture and fail with `NullPointerException`. Forgetting `@Nested` entirely is the mirror image: an inner class without the annotation is simply not discovered, and its tests quietly never run — which looks like a passing build.

  • What does @Nested give you that simply extracting those tests into a separate top-level test class would not?
    A separate top-level class cannot see the enclosing class's fixture, so you would duplicate or extract the setup into a base class or helper. @Nested keeps the fixture layered and local: the outer @BeforeEach still runs, and the report shows the scenario as a child of the subject rather than as an unrelated class. The tradeoff is file size — one class can grow long, and at some point splitting by subject beats nesting by scenario.
  • Can @Nested classes themselves contain @Nested classes, and is there a practical limit?
    Yes, nesting depth is unlimited and each level contributes its own lifecycle callbacks and display name, applied outside-in. In practice two or three levels is the readability limit: beyond that the setup for a given test is spread across so many @BeforeEach methods that a reader cannot reconstruct the state without scrolling. Deep trees also produce very long indicative-sentence display names.
  • If a nested test mutates a field of the outer class, can it affect another test?
    Not with the default PER_METHOD lifecycle: Jupiter creates a fresh outer instance and a fresh inner instance for every test method, so mutations die with the test. The exception is @TestInstance(PER_CLASS) or static/shared state such as static fields or an external database, where the usual isolation concerns apply.

Think of the outer class as a stage set and each @Nested class as a scene that adds props to it: the crew rebuilds the whole set for every take, then dresses it for the scene before the actors say their lines.

saying these in an interview costs you the question

  • Saying @Nested classes must be static (a habit carried over from JUnit 4's Enclosed runner)
  • Claiming the enclosing class's @BeforeEach does not run for tests inside a @Nested class
  • Assuming one outer instance is shared by all nested tests, so state leaks between them
  • Believing an inner class is discovered as a nested container even without @Nested — it is silently ignored and its tests never run
  • Declaring the nested class private, then wondering why nothing is discovered

context

open as a page

For a test method declared inside a JUnit 5 @Nested class, in what order do the enclosing and nested lifecycle callbacks run, how many objects does the framework create, and what does it take to declare @BeforeAll inside that nested class?

level: middleimportance: must knowfreq 40%

basics

~20 s

@BeforeEach runs outside-in (outer then nested), @AfterEach inside-out. With the default per-method lifecycle each test gets a fresh outer instance plus a fresh nested instance. @BeforeAll must be static, which needs Java 16+ or @TestInstance(PER_CLASS) on the nested class.

open as a page

In a JUnit 5 test class, what happens if you declare the nested test class as static — with or without @Nested — and how does that differ from a non-static inner class?

level: middleimportance: should knowfreq 30%

basics

~20 s

A static nested class is not a child container. It behaves as an independent top-level test class (reported as Outer$Inner): no enclosing instance, so no outer fields, no outer @BeforeEach, no inherited class-level configuration. @Nested on it does not make it nested.

open as a page

How do @DisplayName values compose across the levels of a JUnit 5 nested test tree, and what does @DisplayNameGeneration with DisplayNameGenerator.IndicativeSentences change about the reported names?

level: seniorimportance: should knowfreq 25%

basics

~20 s

Each container and method carries its own display name, and reports render them as a tree: outer > nested > test. IndicativeSentences instead concatenates the enclosing display names with the test's into one sentence. Display names are cosmetic only — filtering and reruns use unique IDs built from class and method names.

open as a page

How do you decide whether a large JUnit 5 test class should be organized with @Nested groupings or split into several separate top-level test classes?

level: principalimportance: should knowfreq 22%

basics

~20 s

Nest when the tests share one subject and differ only by state, so each nested level adds a slice of fixture. Split when the groups share no fixture, the file grows unreadable, or the groups are owned or evolve separately. Keep nesting two levels deep at most.

open as a page