One of the most powerful features of @Nested test classes is their ability to inherit and share state from outer classes. This enables you to set up complex test environments once in the outer class and reuse that setup across multiple nested test contexts, eliminating code duplication.
State Sharing Benefits
By setting up shared resources in the outer class's @BeforeEach or @BeforeAll methods, nested classes automatically have access to this state. This is very beneficial because because a common setup is defined once and specialized setup can be added at each nested level.
Example
package com.logicbig.example;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.*;
public class NestedClassStateInheritanceTest {
// Shared state initialized in outer class
protected UserRepository userRepository;
protected LocalDateTime testStartTime;
protected List<String> auditLog;
@BeforeEach
void setUpSharedState() {
// Common setup that ALL nested tests will inherit
testStartTime = LocalDateTime.now();
auditLog = new ArrayList<>();
userRepository = new UserRepository();
// Initialize with common test data
userRepository.addUser(new User("admin", "admin@example.com", User.Role.ADMIN));
userRepository.addUser(new User("user1", "user1@example.com", User.Role.USER));
auditLog.add("Outer setup completed at " + testStartTime);
System.out.println("✓ Outer @BeforeEach: Shared state initialized");
}
@AfterEach
void tearDownSharedState() {
auditLog.add("Test completed at " + LocalDateTime.now());
System.out.println("Audit Log entries: " + auditLog.size());
}
@Test
void outerTest_sharedStateAvailable() {
// Outer tests can use the shared state
assertNotNull(userRepository);
assertNotNull(testStartTime);
assertEquals(2, userRepository.count());
System.out.println("Outer test accessing shared repository");
}
@Nested
class UserAuthenticationTests {
private String authToken;
@BeforeEach
void setUpAuthentication() {
// Additional setup specific to authentication tests
// Outer class state is already available!
authToken = "token-" + System.currentTimeMillis();
auditLog.add("Auth setup: token generated");
System.out.println("✓ Auth @BeforeEach: Using shared userRepository with "
+ userRepository.count() + " users");
}
@Test
void authenticateAdminUser() {
// Accessing outer class state
User admin = userRepository.findByUsername("admin");
assertNotNull(admin);
assertEquals(User.Role.ADMIN, admin.getRole());
// Using nested class specific state
assertNotNull(authToken);
assertTrue(authToken.startsWith("token-"));
auditLog.add("Admin authenticated with token");
System.out.println("Auth test using shared repository and local authToken");
}
@Test
void authenticateRegularUser() {
User user = userRepository.findByUsername("user1");
assertNotNull(user);
assertEquals(User.Role.USER, user.getRole());
auditLog.add("User authenticated");
}
@Nested
class PasswordValidationTests {
@BeforeEach
void setUpPasswordValidation() {
// Can access both outer and parent nested class state
auditLog.add("Password validation setup");
System.out.println("✓ Password @BeforeEach: Has access to authToken? " +
(authToken != null));
}
@Test
void validatePasswordStrength() {
// Accessing state from all ancestor levels
assertNotNull(userRepository); // From outer class
assertNotNull(authToken); // From parent nested class
assertNotNull(testStartTime); // From outer class
User user = userRepository.findByUsername("user1");
assertTrue(user.validatePassword("StrongPass123!"));
auditLog.add("Password validated");
System.out.println("Password test accessing state from all ancestor levels");
}
}
}
@Nested
class UserManagementTests {
@BeforeEach
void setUpUserManagement() {
// Different nested class, same shared outer state
// Adding users specific to management tests
userRepository.addUser(new User("manager",
"manager@example.com", User.Role.MANAGER));
auditLog.add("Manager user added for management tests");
System.out.println("✓ Management @BeforeEach: Repository now has "
+ userRepository.count() + " users");
}
@Test
void createNewUser() {
int initialCount = userRepository.count();
User newUser = new User("newuser", "new@example.com", User.Role.USER);
userRepository.addUser(newUser);
assertEquals(initialCount + 1, userRepository.count());
assertNotNull(userRepository.findByUsername("newuser"));
auditLog.add("New user created");
}
@Test
void deleteUser() {
assertTrue(userRepository.deleteUser("user1"));
assertNull(userRepository.findByUsername("user1"));
auditLog.add("User deleted");
}
@Nested
class BulkOperationsTests {
@Test
void bulkUserImport() {
// Accessing and modifying shared state
int originalCount = userRepository.count();
// Simulate bulk import
for (int i = 1; i <= 5; i++) {
User user = new User("bulk" + i, "bulk" + i +
"@example.com", User.Role.USER);
userRepository.addUser(user);
}
assertEquals(originalCount + 5, userRepository.count());
auditLog.add("Bulk import completed: added 5 users");
System.out.println("Bulk test modified shared repository");
}
}
}
@Nested
class AuditLogTests {
@Test
void verifyAuditLogIntegration() {
// Demonstrates how all tests contribute to shared audit log
assertNotNull(auditLog);
System.out.println("Audit log entries from all tests: " + auditLog);
// The audit log contains entries from all nested test setups
assertTrue(auditLog.size() > 0);
}
}
}
// Supporting classes for the example
class User {
enum Role {ADMIN, USER, MANAGER}
private final String username;
private final String email;
private final Role role;
public User(String username, String email, Role role) {
this.username = username;
this.email = email;
this.role = role;
}
public String getUsername() {return username;}
public String getEmail() {return email;}
public Role getRole() {return role;}
public boolean validatePassword(String password) {
return password != null && password.length() >= 8;
}
}
class UserRepository {
private final Map<String, User> users = new HashMap<>();
public void addUser(User user) {
users.put(user.getUsername(), user);
}
public User findByUsername(String username) {
return users.get(username);
}
public boolean deleteUser(String username) {
return users.remove(username) != null;
}
public int count() {
return users.size();
}
}
mvn test -Dtest=NestedClassStateInheritanceTest OutputD:\example-projects\junit-5\nested-classes\junit-5-nested-class-state-sharing>mvn test -Dtest=NestedClassStateInheritanceTest [INFO] Scanning for projects... [INFO] [INFO] ----------< com.logicbig.example:nested-class-state-sharing >----------- [INFO] Building nested-class-state-sharing 1.0-SNAPSHOT [INFO] from pom.xml [INFO] --------------------------------[ jar ]--------------------------------- [INFO] [INFO] --- resources:3.3.1:resources (default-resources) @ nested-class-state-sharing --- [WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] skip non existing resourceDirectory D:\example-projects\junit-5\nested-classes\junit-5-nested-class-state-sharing\src\main\resources [INFO] [INFO] --- compiler:3.11.0:compile (default-compile) @ nested-class-state-sharing --- [INFO] No sources to compile [INFO] [INFO] --- resources:3.3.1:testResources (default-testResources) @ nested-class-state-sharing --- [WARNING] Using platform encoding (Cp1252 actually) to copy filtered resources, i.e. build is platform dependent! [INFO] skip non existing resourceDirectory D:\example-projects\junit-5\nested-classes\junit-5-nested-class-state-sharing\src\test\resources [INFO] [INFO] --- compiler:3.11.0:testCompile (default-testCompile) @ nested-class-state-sharing --- [INFO] Changes detected - recompiling the module! :input tree [WARNING] File encoding has not been set, using platform encoding Cp1252, i.e. build is platform dependent! [INFO] Compiling 1 source file with javac [debug target 17] to target\test-classes [INFO] [INFO] --- surefire:3.5.0:test (default-test) @ nested-class-state-sharing --- [INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider [INFO] [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- ✓ Outer @BeforeEach: Shared state initialized Outer test accessing shared repository Audit Log entries: 2 ✓ Outer @BeforeEach: Shared state initialized Audit log entries from all tests: [Outer setup completed at 2025-12-06T14:06:53.411174900] Audit Log entries: 2 ✓ Outer @BeforeEach: Shared state initialized ✓ Management @BeforeEach: Repository now has 3 users Audit Log entries: 4 ✓ Outer @BeforeEach: Shared state initialized ✓ Management @BeforeEach: Repository now has 3 users Audit Log entries: 4 ✓ Outer @BeforeEach: Shared state initialized ✓ Management @BeforeEach: Repository now has 3 users Bulk test modified shared repository Audit Log entries: 4 ✓ Outer @BeforeEach: Shared state initialized ✓ Auth @BeforeEach: Using shared userRepository with 2 users Audit Log entries: 4 ✓ Outer @BeforeEach: Shared state initialized ✓ Auth @BeforeEach: Using shared userRepository with 2 users Auth test using shared repository and local authToken Audit Log entries: 4 ✓ Outer @BeforeEach: Shared state initialized ✓ Auth @BeforeEach: Using shared userRepository with 2 users ✓ Password @BeforeEach: Has access to authToken? true Password test accessing state from all ancestor levels Audit Log entries: 5 [INFO] +--com.logicbig.example.NestedClassStateInheritanceTest - 0.164 ss [INFO] | '-- [OK] outerTest_sharedStateAvailable - 0.066 ss [INFO] +--.--com.logicbig.example.NestedClassStateInheritanceTest$AuditLogTests - 0.009 ss [INFO] | | '-- [OK] verifyAuditLogIntegration - 0.004 ss [INFO] +--.--com.logicbig.example.NestedClassStateInheritanceTest$UserManagementTests - 0.009 ss [INFO] | | +-- [OK] createNewUser - 0.003 ss [INFO] | | '-- [OK] deleteUser - 0.004 ss [INFO] | '--.--com.logicbig.example.NestedClassStateInheritanceTest$UserManagementTests$BulkOperationsTests - 0.016 ss [INFO] | | '-- [OK] bulkUserImport - 0.002 ss [INFO] +--.--com.logicbig.example.NestedClassStateInheritanceTest$UserAuthenticationTests - 0.009 ss [INFO] | | +-- [OK] authenticateRegularUser - 0.006 ss [INFO] | | '-- [OK] authenticateAdminUser - 0.001 ss [INFO] | '-----com.logicbig.example.NestedClassStateInheritanceTest$UserAuthenticationTests$PasswordValidationTests - 0.016 ss [INFO] | '-- [OK] validatePasswordStrength - 0.002 ss [INFO] [INFO] Results: [INFO] [INFO] Tests run: 8, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 4.402 s [INFO] Finished at: 2025-12-06T14:06:53+08:00 [INFO] ------------------------------------------------------------------------
Example ProjectDependencies and Technologies Used: - junit-jupiter-engine 6.0.1 (Module "junit-jupiter-engine" of JUnit)
Version Compatibility: 5.0.0 - 6.0.1 Version compatibilities of junit-jupiter-engine with this example:
- 5.0.0
- 5.0.1
- 5.0.2
- 5.0.3
- 5.1.0
- 5.1.1
- 5.2.0
- 5.3.0
- 5.3.1
- 5.3.2
- 5.4.0
- 5.4.1
- 5.4.2
- 5.5.0
- 5.5.1
- 5.5.2
- 5.6.0
- 5.6.1
- 5.6.2
- 5.6.3
- 5.7.0
- 5.7.1
- 5.7.2
- 5.8.0
- 5.8.1
- 5.8.2
- 5.9.0
- 5.9.1
- 5.9.2
- 5.9.3
- 5.10.0
- 5.10.1
- 5.10.2
- 5.10.3
- 5.10.4
- 5.10.5
- 5.11.0
- 5.11.1
- 5.11.2
- 5.11.3
- 5.11.4
- 5.12.0
- 5.12.1
- 5.12.2
- 5.13.0
- 5.13.1
- 5.13.2
- 5.13.3
- 5.13.4
- 5.14.0
- 5.14.1
- 6.0.0
- 6.0.1
Versions in green have been tested.
- JDK 25
- Maven 3.9.11
|
|