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:
@BeforeAllfor one-time setup before all tests@BeforeEachfor setup before every test@Testfor the actual test@AfterEachfor cleanup after every test@AfterAllfor 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.
