Organizing Tests with Nested Test Classes
Nested test classes are a great way to group related tests inside a single test class. In Java, this is commonly done with JUnit 5 using @Nested.
They help you structure tests around:
- A specific method
- A scenario
- A state of the object under test
- Success vs failure cases
- Different user roles or permissions
- Different validation paths
Basic Example
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CalculatorTest {
private final Calculator calculator = new Calculator();
@Nested
class AddTests {
@Test
void shouldAddTwoPositiveNumbers() {
int result = calculator.add(2, 3);
assertEquals(5, result);
}
@Test
void shouldAddNegativeNumbers() {
int result = calculator.add(-2, -3);
assertEquals(-5, result);
}
}
@Nested
class DivideTests {
@Test
void shouldDivideTwoNumbers() {
int result = calculator.divide(10, 2);
assertEquals(5, result);
}
@Test
void shouldThrowWhenDividingByZero() {
// test exception case here
}
}
}
A Common Organization Style
A useful pattern is:
class UserServiceTest {
@Nested
class CreateUser {
@Test
void shouldCreateUserWhenInputIsValid() {
}
@Test
void shouldRejectDuplicateEmail() {
}
@Test
void shouldRejectInvalidEmail() {
}
}
@Nested
class UpdateUser {
@Test
void shouldUpdateUserWhenUserExists() {
}
@Test
void shouldThrowWhenUserDoesNotExist() {
}
}
@Nested
class DeleteUser {
@Test
void shouldDeleteUserWhenUserExists() {
}
@Test
void shouldDoNothingWhenUserDoesNotExist() {
}
}
}
This makes the test report easier to read:
UserServiceTest
├─ CreateUser
│ ├─ shouldCreateUserWhenInputIsValid
│ ├─ shouldRejectDuplicateEmail
│ └─ shouldRejectInvalidEmail
├─ UpdateUser
│ ├─ shouldUpdateUserWhenUserExists
│ └─ shouldThrowWhenUserDoesNotExist
└─ DeleteUser
├─ shouldDeleteUserWhenUserExists
└─ shouldDoNothingWhenUserDoesNotExist
Using @BeforeEach in Nested Classes
Each nested class can have its own setup.
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
class OrderServiceTest {
private OrderService orderService;
@BeforeEach
void setUp() {
orderService = new OrderService();
}
@Nested
class WhenOrderIsNew {
private Order order;
@BeforeEach
void setUp() {
order = new Order("NEW");
}
@Test
void shouldAllowCancellation() {
// test new order cancellation
}
@Test
void shouldAllowPayment() {
// test new order payment
}
}
@Nested
class WhenOrderIsShipped {
private Order order;
@BeforeEach
void setUp() {
order = new Order("SHIPPED");
}
@Test
void shouldNotAllowCancellation() {
// test shipped order cancellation
}
}
}
The outer @BeforeEach runs before the nested class @BeforeEach.
Execution order is:
OrderServiceTest.setUp()
WhenOrderIsNew.setUp()
test method
Recommended Naming Styles
Option 1: Method-based grouping
class ProductServiceTest {
@Nested
class FindById {
@Test
void shouldReturnProductWhenFound() {
}
@Test
void shouldThrowWhenProductDoesNotExist() {
}
}
@Nested
class Save {
@Test
void shouldSaveValidProduct() {
}
@Test
void shouldRejectProductWithoutName() {
}
}
}
Option 2: Scenario-based grouping
class CheckoutServiceTest {
@Nested
class WhenCartIsEmpty {
@Test
void shouldRejectCheckout() {
}
}
@Nested
class WhenCartHasItems {
@Test
void shouldCreateOrder() {
}
@Test
void shouldClearCartAfterCheckout() {
}
}
}
Both are valid. For service classes, I usually prefer method-based grouping. For complex domain behavior, scenario-based grouping often reads better.
Best Practices
Do
- Use
@Nestedto group related test cases. - Keep nesting shallow, usually one level deep.
- Use descriptive nested class names.
- Use
@BeforeEachinside nested classes for scenario-specific setup. - Keep test method names behavior-focused.
Example:
@Test
void shouldThrowExceptionWhenEmailAlreadyExists() {
}
Avoid
- Deep nesting like 3–4 levels unless absolutely necessary.
- Putting unrelated tests in the same nested class.
- Sharing too much mutable state between nested classes.
- Making nested class names vague, such as
Tests1orValidations.
With Spring Boot / Spring MVC Tests
Nested tests also work with Spring test annotations.
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.MockMvc;
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@Nested
class CreateUser {
@Test
void shouldReturnCreatedWhenRequestIsValid() throws Exception {
// mockMvc.perform(...)
}
@Test
void shouldReturnBadRequestWhenEmailIsInvalid() throws Exception {
// mockMvc.perform(...)
}
}
@Nested
class GetUser {
@Test
void shouldReturnUserWhenFound() throws Exception {
// mockMvc.perform(...)
}
@Test
void shouldReturnNotFoundWhenUserDoesNotExist() throws Exception {
// mockMvc.perform(...)
}
}
}
Rule of Thumb
Use nested test classes when your flat test class starts looking like this:
@Test
void createUserShouldSucceedWhenInputIsValid() {
}
@Test
void createUserShouldFailWhenEmailExists() {
}
@Test
void updateUserShouldSucceedWhenUserExists() {
}
@Test
void updateUserShouldFailWhenUserDoesNotExist() {
}
Refactor it into:
class UserServiceTest {
@Nested
class CreateUser {
}
@Nested
class UpdateUser {
}
}
That usually makes the tests easier to scan, maintain, and understand.
