When your test class grows beyond a handful of tests, it becomes hard to navigate.
@Nested lets you group related tests into inner classes, creating a
logical hierarchy. It is like organizing your tests into chapters and sections.
Basic @Nested Structure
class StackTest {
private Stack<String> stack;
@BeforeEach
void setUp() {
stack = new Stack<>();
}
@Nested
@DisplayName("when stack is empty")
class EmptyStack {
@Test
@DisplayName("should have size zero")
void sizeIsZero() {
assertEquals(0, stack.size());
}
@Test
@DisplayName("should return true for isEmpty()")
void isEmpty() {
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("should throw EmptyStackException on pop")
void popThrowsException() {
assertThrows(EmptyStackException.class,
() -> stack.pop());
}
}
@Nested
@DisplayName("when stack has elements")
class StackWithElements {
@BeforeEach
void pushElements() {
stack.push("first");
stack.push("second");
stack.push("third");
}
@Test
@DisplayName("should have correct size")
void correctSize() {
assertEquals(3, stack.size());
}
@Test
@DisplayName("should return last pushed element on pop")
void popReturnsLastElement() {
assertEquals("third", stack.pop());
}
@Test
@DisplayName("should peek at top element without removing")
void peekReturnsTopElement() {
assertEquals("third", stack.peek());
assertEquals(3, stack.size()); // Size unchanged
}
}
}
How @Nested Works
Each @Nested class is a separate test class with its own lifecycle.
The key thing to understand:
class MyTest {
@BeforeEach
void outerSetUp() {
System.out.println("Outer @BeforeEach");
}
@Nested
class InnerGroup {
@BeforeEach
void innerSetUp() {
System.out.println("Inner @BeforeEach");
}
@Test
void myTest() {
System.out.println("Test");
}
}
}
// Output:
// Outer @BeforeEach
// Inner @BeforeEach
// Test
The outer @BeforeEach runs first, then the inner one. This means
inner classes inherit the setup from outer classes.
Deep Nesting
You can nest as deep as you want (though 2-3 levels is usually enough):
class OrderTest {
@Nested
class CreatingOrder {
@Test
void orderIsCreated() { }
@Nested
class WithValidItems {
@Test
void orderContainsItems() { }
}
@Nested
class WithInvalidItems {
@Test
void orderIsRejected() { }
}
}
}
Nested Test Reporting
JUnit produces a nested report structure. Here is what it looks like with @DisplayName:
OrderTest
โโโ CreatingOrder
โ โโโ order is created
โ โโโ WithValidItems
โ โ โโโ order contains items
โ โโโ WithInvalidItems
โ โโโ order is rejected
This is far more readable than a flat list of 50 test methods.
Practical Example: Testing a Bank Account
class BankAccountTest {
private BankAccount account;
@BeforeEach
void setUp() {
account = new BankAccount(1000);
}
@Nested
@DisplayName("deposit")
class Deposit {
@Test
@DisplayName("should increase balance")
void increasesBalance() {
account.deposit(500);
assertEquals(1500, account.getBalance());
}
@Test
@DisplayName("should reject negative amount")
void rejectsNegativeAmount() {
assertThrows(IllegalArgumentException.class,
() -> account.deposit(-100));
}
}
@Nested
@DisplayName("withdraw")
class Withdraw {
@Test
@DisplayName("should decrease balance when sufficient funds")
void decreasesBalance() {
account.withdraw(300);
assertEquals(700, account.getBalance());
}
@Test
@DisplayName("should throw when insufficient funds")
void throwsOnInsufficientFunds() {
assertThrows(InsufficientFundsException.class,
() -> account.withdraw(2000));
}
@Nested
@DisplayName("when balance is zero")
class ZeroBalance {
@BeforeEach
void drainAccount() {
account.withdraw(1000);
}
@Test
@DisplayName("should always throw")
void alwaysThrows() {
assertThrows(InsufficientFundsException.class,
() -> account.withdraw(1));
}
}
}
@Nested
@DisplayName("transfer")
class Transfer {
@Test
@DisplayName("should move money between accounts")
void movesMoneyBetweenAccounts() {
BankAccount other = new BankAccount(500);
account.transfer(other, 200);
assertEquals(800, account.getBalance());
assertEquals(700, other.getBalance());
}
}
}
Best Practices
- Use @Nested to group tests by feature or scenario
- Give each nested class a descriptive @DisplayName
- Use @BeforeEach in nested classes for group-specific setup
- Keep nesting to 2-3 levels max
- Avoid tests that depend on the execution order within a nested class