Close

JUnit 5 - Nested Class State Sharing

[Last Updated: Dec 6, 2025]

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

Output

D:\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 Project

Dependencies and Technologies Used:

  • junit-jupiter-engine 6.0.1 (Module "junit-jupiter-engine" of JUnit)
     Version Compatibility: 5.0.0 - 6.0.1Version List
    ×

    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

Nested Class State Inheritance Select All Download
  • junit-5-nested-class-state-sharing
    • src
      • test
        • java
          • com
            • logicbig
              • example
                • NestedClassStateInheritanceTest.java

    See Also