As applications evolve, testing scenarios become more complex. A flat list of test methods may work for small projects, but it quickly turns into clutter when handling advanced workflows, domain-driven logic, or multi-layered microservices. To solve this, JUnit 5 introduces nested tests via the @Nested
annotation, allowing developers to group related tests and create hierarchical structures that mirror real-world business logic.
In this tutorial, we’ll dive into nested tests in JUnit 5, explain why they matter, walk through practical examples, and discuss best practices for structuring complex test suites in enterprise Java projects.
Why Nested Tests Matter
- Clarity: Related test cases are grouped together logically.
- Readability: Tests follow a hierarchy that mirrors the structure of the code.
- Maintainability: Easier to extend and modify specific scenarios.
- Scalability: Keeps large test classes manageable.
- CI/CD Readiness: Suites remain organized for continuous integration pipelines.
Think of nested tests like chapters in a book: instead of having one giant page of text, you split your story into well-organized sections.
Basic Example of Nested Tests
import org.junit.jupiter.api.*;
class UserServiceTest {
private UserService userService;
@BeforeEach
void setUp() {
userService = new UserService();
}
@Nested
class CreateUserTests {
@Test
void shouldCreateUserSuccessfully() {
User user = userService.createUser("Alice");
Assertions.assertNotNull(user);
}
@Test
void shouldFailWhenNameIsEmpty() {
Assertions.assertThrows(IllegalArgumentException.class,
() -> userService.createUser(""));
}
}
@Nested
class DeleteUserTests {
@Test
void shouldDeleteExistingUser() {
userService.createUser("Bob");
userService.deleteUser("Bob");
Assertions.assertTrue(userService.isDeleted("Bob"));
}
@Test
void shouldThrowWhenUserNotFound() {
Assertions.assertThrows(UserNotFoundException.class,
() -> userService.deleteUser("Unknown"));
}
}
}
Here, CreateUserTests
and DeleteUserTests
group tests logically, improving readability.
Lifecycle in Nested Tests
@BeforeAll
and@AfterAll
must bestatic
by default.- Each
@Nested
class has its own@BeforeEach
and@AfterEach
lifecycle. - Parent and nested lifecycles are executed in order.
@Nested
class NestedExample {
@BeforeEach
void beforeEach() {
System.out.println("Before each nested test");
}
@Test
void sampleTest() {
System.out.println("Running nested test");
}
}
Combining Nested Tests with Parameterized Tests
Nested tests also work with parameterized tests, providing rich data-driven scenarios.
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
class MathUtilsTest {
@Nested
class FactorialTests {
@ParameterizedTest
@ValueSource(ints = {0, 1, 5})
void shouldCalculateFactorial(int input) {
int result = factorial(input);
Assertions.assertTrue(result >= 1);
}
int factorial(int n) {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
}
}
Real-World Use Cases
-
Spring Boot Apps
Group tests by REST controllers, service layers, and repositories. -
Microservices
Use nested tests to separate contract testing, integration with Kafka, and DB verification with Testcontainers. -
Legacy Codebases
Gradually organize chaotic tests by introducing nested structures for domain-specific logic.
Nested Tests with Mockito and Testcontainers
Mockito Example
import org.junit.jupiter.api.*;
import org.mockito.Mockito;
class OrderServiceTest {
private OrderRepository repository;
private OrderService service;
@BeforeEach
void setUp() {
repository = Mockito.mock(OrderRepository.class);
service = new OrderService(repository);
}
@Nested
class PlaceOrderTests {
@Test
void shouldSaveOrder() {
Order order = new Order("Book");
service.placeOrder(order);
Mockito.verify(repository).save(order);
}
}
}
Testcontainers Example
import org.junit.jupiter.api.*;
import org.testcontainers.containers.PostgreSQLContainer;
class DatabaseIntegrationTest {
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
@BeforeAll
static void setupAll() {
postgres.start();
}
@Nested
class UserTableTests {
@Test
void shouldInsertUser() {
System.out.println("JDBC URL: " + postgres.getJdbcUrl());
Assertions.assertTrue(postgres.isRunning());
}
}
@AfterAll
static void teardownAll() {
postgres.stop();
}
}
Best Practices
- Use
@Nested
only when grouping adds value. - Avoid overly deep nesting — 2–3 levels max.
- Use descriptive class names (
CreateUserTests
, notInnerClass1
). - Combine with tags (
@Tag("integration")
) for CI/CD filtering. - Treat nested classes as units of behavior, not just folders.
Version Tracker
- JUnit 4 → JUnit 5: Nested tests were introduced in JUnit 5 (not available in JUnit 4).
- Mockito Updates: Static and final mocking support aligns well with nested unit testing.
- Testcontainers Growth: Expanded modules for databases, Kafka, LocalStack — perfect for nested integration tests.
Conclusion & Key Takeaways
Nested tests in JUnit 5 bring structure and clarity to complex test scenarios. They allow developers to group related tests, maintain readable suites, and scale testing for modern enterprise applications.
Key Takeaways:
- Use nested tests for logical grouping.
- Combine with parameterized tests, Mockito, and Testcontainers.
- Avoid deep hierarchies; focus on clarity.
- Nested tests improve maintainability and CI/CD readiness.
FAQ
1. What is the main benefit of nested tests in JUnit 5?
They group related test cases together for clarity and maintainability.
2. Can nested tests have their own lifecycle methods?
Yes, @BeforeEach
and @AfterEach
work independently in each nested class.
3. How deep can nested tests go?
Technically unlimited, but best practice is no more than 2–3 levels.
4. Do nested tests work with parameterized tests?
Yes, nested classes fully support @ParameterizedTest
.
5. Can I use assumptions inside nested tests?
Yes, you can use assumeTrue
or assumingThat
as in normal tests.
6. Do nested tests work with Mockito?
Absolutely, mocks can be initialized in each nested class.
7. Can I run only a specific nested test class?
Yes, IDEs and build tools like Maven/Gradle support running nested tests.
8. How do nested tests appear in test reports?
They show as hierarchical structures, improving readability.
9. Can I use Testcontainers with nested tests?
Yes, containers can be started in parent or nested lifecycles.
10. Should I migrate JUnit 4 tests to nested tests?
Yes, if grouping improves clarity and maintainability in large test classes.