How do I organize tests with nested test classes?

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 @Nested to group related test cases.
  • Keep nesting shallow, usually one level deep.
  • Use descriptive nested class names.
  • Use @BeforeEach inside 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 Tests1 or Validations.

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.

How do I add display names to JUnit tests?

In JUnit 5, you can add readable names to your tests using the @DisplayName annotation.

@DisplayName lets you show a friendly, human-readable test name in test reports and IDE test runners instead of relying only on the Java method name.

Example

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class CalculatorTest {

    @Test
    @DisplayName("Adding two positive numbers returns their sum")
    void shouldAddTwoPositiveNumbers() {
        int result = 2 + 3;

        assertEquals(5, result);
    }
}

The method name is still:

shouldAddTwoPositiveNumbers

But the test report can display:

Adding two positive numbers returns their sum

Add Display Names to Test Classes

You can also add @DisplayName to the test class itself:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertTrue;

@DisplayName("Password validation tests")
class PasswordValidatorTest {

    @Test
    @DisplayName("Valid password should be accepted")
    void shouldAcceptValidPassword() {
        assertTrue(true);
    }

    @Test
    @DisplayName("Short password should be rejected")
    void shouldRejectShortPassword() {
        assertTrue(true);
    }
}

Using Emojis or Symbols

JUnit 5 display names can include spaces, punctuation, and even emojis:

@Test
@DisplayName("✅ Valid email should pass validation")
void shouldAcceptValidEmail() {
    assertTrue(true);
}

Use this carefully. Emojis can make reports more readable, but they may not be appropriate for every team or CI environment.

Display Names for Nested Tests

@DisplayName is especially useful with @Nested tests:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

@DisplayName("Shopping cart")
class ShoppingCartTest {

    @Nested
    @DisplayName("when adding items")
    class AddingItems {

        @Test
        @DisplayName("updates the total item count")
        void shouldUpdateItemCount() {
            int itemCount = 1 + 2;

            assertEquals(3, itemCount);
        }
    }
}

This can produce a readable test structure such as:

Shopping cart
 └─ when adding items
    └─ updates the total item count

Display Names for Parameterized Tests

For parameterized tests, you can customize each invocation name:

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import static org.junit.jupiter.api.Assertions.assertTrue;

class NumberTest {

    @ParameterizedTest(name = "{0} should be positive")
    @ValueSource(ints = {1, 5, 10})
    void shouldBePositive(int number) {
        assertTrue(number > 0);
    }
}

This can display:

1 should be positive
5 should be positive
10 should be positive

You can also combine it with @DisplayName:

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import static org.junit.jupiter.api.Assertions.assertTrue;

class NumberTest {

    @DisplayName("Positive number validation")
    @ParameterizedTest(name = "Value {0} is positive")
    @ValueSource(ints = {1, 5, 10})
    void shouldBePositive(int number) {
        assertTrue(number > 0);
    }
}

Required Dependency

Make sure you are using JUnit Jupiter, which is JUnit 5:

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.13.4</version>
    <scope>test</scope>
</dependency>

For Gradle:

testImplementation 'org.junit.jupiter:junit-jupiter:5.13.4'

test {
    useJUnitPlatform()
}

Summary

Use @DisplayName when you want test output to be more readable:

@Test
@DisplayName("User should be created when input is valid")
void shouldCreateUserWhenInputIsValid() {
}

The Java method name remains valid and searchable, while the test report shows a clearer description.

How do I disable a test with @Disabled annotation?

In JUnit 5, you can disable a test by annotating it with @Disabled from the org.junit.jupiter.api package.

Disable a Single Test Method

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

class CalculatorTest {

    @Test
    @Disabled
    void divisionByZeroTest() {
        // This test will be skipped
    }
}

When you run the test suite, this test will not be executed.

Add a Reason

It is a good practice to include a reason so other developers know why the test is disabled.

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

class CalculatorTest {

    @Test
    @Disabled("Temporarily disabled until division-by-zero handling is fixed")
    void divisionByZeroTest() {
        // This test will be skipped
    }
}

Disable an Entire Test Class

You can also place @Disabled on a test class to skip all tests inside it.

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

@Disabled("Disabled while refactoring the calculator module")
class CalculatorTest {

    @Test
    void additionTest() {
        // This test will be skipped
    }

    @Test
    void subtractionTest() {
        // This test will also be skipped
    }
}

Summary

Use:

@Disabled

or preferably:

@Disabled("Reason why this test is disabled")

The required import is:

import org.junit.jupiter.api.Disabled;

@Disabled is useful for temporarily skipping tests that are incomplete, unstable, or depend on functionality that is not ready yet.

How do I understand the JUnit test lifecycle?

The JUnit test lifecycle describes the order in which JUnit creates test objects, runs setup code, executes test methods, and performs cleanup.

Understanding this lifecycle helps you write tests that are clean, predictable, and easy to maintain.

This guide focuses on JUnit 5, also known as JUnit Jupiter.


1. What Is the JUnit Test Lifecycle?

When JUnit runs a test class, it does more than just execute methods annotated with @Test.

It may also run special lifecycle methods before and after your tests.

Common lifecycle annotations are:

Annotation When It Runs
@BeforeAll Once before all test methods
@BeforeEach Before each test method
@Test The actual test method
@AfterEach After each test method
@AfterAll Once after all test methods

The typical order is:

@BeforeAll

@BeforeEach
@Test
@AfterEach

@BeforeEach
@Test
@AfterEach

@BeforeEach
@Test
@AfterEach

@AfterAll

2. Basic Lifecycle Example

Here is a simple example showing the order of execution:

package org.kodejava.junit;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class LifecycleTest {

    @BeforeAll
    static void beforeAll() {
        System.out.println("Before all tests");
    }

    @BeforeEach
    void beforeEach() {
        System.out.println("Before each test");
    }

    @Test
    void firstTest() {
        System.out.println("First test");
    }

    @Test
    void secondTest() {
        System.out.println("Second test");
    }

    @AfterEach
    void afterEach() {
        System.out.println("After each test");
    }

    @AfterAll
    static void afterAll() {
        System.out.println("After all tests");
    }
}

Example output may look like this:

Before all tests
Before each test
First test
After each test
Before each test
Second test
After each test
After all tests

The exact order of firstTest() and secondTest() is not guaranteed unless you explicitly configure test method ordering.


3. @BeforeAll

The @BeforeAll method runs once before all test methods in the test class.

It is commonly used for expensive setup that should happen only one time, such as:

  • Starting a test server
  • Creating shared test data
  • Initializing a database connection
  • Loading configuration

Example:

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

class DatabaseTest {

    @BeforeAll
    static void connectToDatabase() {
        System.out.println("Connect to database");
    }

    @Test
    void testOne() {
        System.out.println("Run test one");
    }

    @Test
    void testTwo() {
        System.out.println("Run test two");
    }
}

In JUnit 5, @BeforeAll methods are usually static.


4. @BeforeEach

The @BeforeEach method runs before every test method.

This is useful when each test needs a fresh object or a clean starting state.

Example:

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class CalculatorTest {

    private Calculator calculator;

    @BeforeEach
    void setUp() {
        calculator = new Calculator();
    }

    @Test
    void addReturnsSum() {
        int result = calculator.add(2, 3);

        assertEquals(5, result);
    }

    @Test
    void addCanReturnNegativeResult() {
        int result = calculator.add(-2, -3);

        assertEquals(-5, result);
    }
}

Here, JUnit calls setUp() before each test method. Each test gets a properly initialized Calculator.


5. @Test

The @Test annotation marks a method as a test method.

Example:

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class SimpleTest {

    @Test
    void twoPlusTwoEqualsFour() {
        assertEquals(4, 2 + 2);
    }
}

A test method should usually follow the Arrange, Act, Assert pattern:

@Test
void addReturnsSum() {
    // Arrange
    Calculator calculator = new Calculator();

    // Act
    int result = calculator.add(2, 3);

    // Assert
    assertEquals(5, result);
}

6. @AfterEach

The @AfterEach method runs after every test method.

It is commonly used for cleanup, such as:

  • Closing files
  • Clearing temporary data
  • Resetting mocks
  • Releasing resources

Example:

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class FileProcessorTest {

    @BeforeEach
    void createTempFile() {
        System.out.println("Create temporary file");
    }

    @Test
    void processFile() {
        System.out.println("Process file");
    }

    @AfterEach
    void deleteTempFile() {
        System.out.println("Delete temporary file");
    }
}

For each test, JUnit runs:

createTempFile()
processFile()
deleteTempFile()

7. @AfterAll

The @AfterAll method runs once after all test methods in the test class have finished.

It is often used to clean up shared resources created in @BeforeAll.

Example:

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

class ServerTest {

    @BeforeAll
    static void startServer() {
        System.out.println("Start server");
    }

    @Test
    void serverResponds() {
        System.out.println("Test server response");
    }

    @AfterAll
    static void stopServer() {
        System.out.println("Stop server");
    }
}

Like @BeforeAll, @AfterAll is usually static.


8. Complete Lifecycle Example

The following example shows all major lifecycle annotations together:

package org.kodejava.junit;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class ShoppingCartTest {

    private ShoppingCart cart;

    @BeforeAll
    static void beforeAllTests() {
        System.out.println("Prepare shared test resources");
    }

    @BeforeEach
    void setUp() {
        cart = new ShoppingCart();
        System.out.println("Create a new shopping cart");
    }

    @Test
    void cartIsEmptyWhenCreated() {
        assertEquals(0, cart.getItemCount());
    }

    @Test
    void addingItemIncreasesItemCount() {
        cart.addItem("Book");

        assertEquals(1, cart.getItemCount());
    }

    @AfterEach
    void tearDown() {
        System.out.println("Clean up after test");
    }

    @AfterAll
    static void afterAllTests() {
        System.out.println("Release shared test resources");
    }
}

Example class being tested:

package org.kodejava.junit;

import java.util.ArrayList;
import java.util.List;

public class ShoppingCart {

    private final List<String> items = new ArrayList<>();

    public void addItem(String item) {
        items.add(item);
    }

    public int getItemCount() {
        return items.size();
    }
}

The lifecycle is:

@BeforeAll

@BeforeEach
@Test cartIsEmptyWhenCreated
@AfterEach

@BeforeEach
@Test addingItemIncreasesItemCount
@AfterEach

@AfterAll

9. JUnit Creates a New Test Instance by Default

By default, JUnit 5 creates a new instance of the test class for each test method.

For example:

import org.junit.jupiter.api.Test;

class CounterTest {

    private int counter = 0;

    @Test
    void firstTest() {
        counter++;
        System.out.println(counter);
    }

    @Test
    void secondTest() {
        counter++;
        System.out.println(counter);
    }
}

You might expect the output to be:

1
2

But because JUnit creates a new test class instance for each test method, the output is more likely:

1
1

This is a good thing. It helps keep tests independent from each other.


10. Why Test Independence Matters

Each test should be able to run:

  • By itself
  • With other tests
  • In any order
  • Repeatedly with the same result

Avoid writing tests that depend on another test running first.

Bad example:

class BadTest {

    private int value = 0;

    @Test
    void firstTest() {
        value = 10;
    }

    @Test
    void secondTest() {
        assertEquals(10, value);
    }
}

This is unreliable because secondTest() depends on state changed by firstTest().

Better example:

class GoodTest {

    private int value;

    @BeforeEach
    void setUp() {
        value = 10;
    }

    @Test
    void firstTest() {
        assertEquals(10, value);
    }

    @Test
    void secondTest() {
        assertEquals(10, value);
    }
}

Each test gets the state it needs from @BeforeEach.


11. When Should You Use Each Lifecycle Annotation?

Use @BeforeEach when setup is needed for every test:

@BeforeEach
void setUp() {
    calculator = new Calculator();
}

Use @AfterEach when cleanup is needed after every test:

@AfterEach
void cleanUp() {
    temporaryFiles.clear();
}

Use @BeforeAll when setup is expensive and can be shared:

@BeforeAll
static void loadLargeTestFile() {
    System.out.println("Load shared test data");
}

Use @AfterAll to release shared resources:

@AfterAll
static void closeConnection() {
    System.out.println("Close shared connection");
}

12. Common Mistakes

Forgetting That @BeforeAll Must Usually Be Static

This will not work in the default lifecycle:

@BeforeAll
void beforeAll() {
    System.out.println("Before all");
}

Use:

@BeforeAll
static void beforeAll() {
    System.out.println("Before all");
}

Sharing Mutable State Between Tests

Avoid relying on state modified by another test.

Instead of this:

private static List<String> names = new ArrayList<>();

Prefer creating fresh state in @BeforeEach:

private List<String> names;

@BeforeEach
void setUp() {
    names = new ArrayList<>();
}

Putting Assertions in Setup Methods

Lifecycle methods should prepare or clean up test state. Assertions usually belong in @Test methods.

Instead of:

@BeforeEach
void setUp() {
    calculator = new Calculator();
    assertNotNull(calculator);
}

Prefer:

@BeforeEach
void setUp() {
    calculator = new Calculator();
}

@Test
void calculatorIsCreated() {
    assertNotNull(calculator);
}

Summary

The JUnit test lifecycle controls how setup, test execution, and cleanup happen.

The common order is:

@BeforeAll
@BeforeEach
@Test
@AfterEach
@BeforeEach
@Test
@AfterEach
@AfterAll

Use:

  • @BeforeAll for one-time setup before all tests
  • @BeforeEach for setup before every test
  • @Test for the actual test
  • @AfterEach for cleanup after every test
  • @AfterAll for one-time cleanup after all tests

The most important rule is: keep tests independent. Each test should prepare its own state and should not depend on another test method running before it.

How do I use @BeforeAll and @AfterAll in JUnit?

In JUnit 5, @BeforeAll and @AfterAll are lifecycle annotations used to run setup and cleanup code once per test class.

They are useful when you need to initialize or clean up expensive shared resources, such as:

  • database connections
  • test containers
  • temporary directories
  • mock servers
  • shared test data
  • application-wide configuration

Basic Rule

By default, methods annotated with @BeforeAll and @AfterAll must be static.

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

class ExampleTest {

    @BeforeAll
    static void setUpBeforeAllTests() {
        System.out.println("Runs once before all tests");
    }

    @AfterAll
    static void cleanUpAfterAllTests() {
        System.out.println("Runs once after all tests");
    }

    @Test
    void firstTest() {
        System.out.println("First test");
    }

    @Test
    void secondTest() {
        System.out.println("Second test");
    }
}

Execution order:

Runs once before all tests
First test
Second test
Runs once after all tests

Compared with @BeforeEach and @AfterEach

@BeforeAll and @AfterAll run once for the whole test class.

@BeforeEach and @AfterEach run before and after every test method.

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class LifecycleTest {

    @BeforeAll
    static void beforeAll() {
        System.out.println("Before all tests");
    }

    @BeforeEach
    void beforeEach() {
        System.out.println("Before each test");
    }

    @Test
    void testOne() {
        System.out.println("Test one");
    }

    @Test
    void testTwo() {
        System.out.println("Test two");
    }

    @AfterEach
    void afterEach() {
        System.out.println("After each test");
    }

    @AfterAll
    static void afterAll() {
        System.out.println("After all tests");
    }
}

Output will be similar to:

Before all tests
Before each test
Test one
After each test
Before each test
Test two
After each test
After all tests

Practical Example

Suppose several tests need the same expensive resource.

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertTrue;

class DatabaseConnectionTest {

    private static FakeDatabaseConnection connection;

    @BeforeAll
    static void openConnection() {
        connection = new FakeDatabaseConnection();
        connection.open();
    }

    @AfterAll
    static void closeConnection() {
        connection.close();
    }

    @Test
    void connectionShouldBeOpen() {
        assertTrue(connection.isOpen());
    }

    @Test
    void canExecuteQuery() {
        assertTrue(connection.execute("SELECT 1"));
    }
}

Here:

  • openConnection() runs once before all tests.
  • The same connection is reused by all test methods.
  • closeConnection() runs once after all tests finish.

Using Non-Static @BeforeAll and @AfterAll

If you do not want these methods to be static, annotate the test class with:

@TestInstance(TestInstance.Lifecycle.PER_CLASS)

Example:

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;

@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class NonStaticLifecycleTest {

    private String sharedValue;

    @BeforeAll
    void beforeAll() {
        sharedValue = "ready";
    }

    @AfterAll
    void afterAll() {
        sharedValue = null;
    }

    @Test
    void testSharedValue() {
        assert sharedValue.equals("ready");
    }
}

With PER_CLASS, JUnit creates one instance of the test class and reuses it for all test methods. Because of that, @BeforeAll and @AfterAll can be instance methods.

When Should You Use Them?

Use @BeforeAll for setup that should happen once, such as:

@BeforeAll
static void startServer() {
    // start shared server
}

Use @AfterAll for cleanup that should happen once, such as:

@AfterAll
static void stopServer() {
    // stop shared server
}

Avoid using them for the per-test state. For that, prefer @BeforeEach.

Important Best Practices

  • Use @BeforeAll only for shared setup.
  • Use @AfterAll to release resources created in @BeforeAll.
  • Avoid modifying the shared state between tests unless you reset it properly.
  • Prefer @BeforeEach when each test needs a fresh object.
  • By default, make @BeforeAll and @AfterAll methods static.
  • Use @TestInstance(TestInstance.Lifecycle.PER_CLASS) only when you specifically want non-static lifecycle methods.

In short:

Annotation Runs When? Usually Static?
@BeforeAll Once before all tests Yes
@AfterAll Once after all tests Yes
@BeforeEach Before each test method No
@AfterEach After each test method No