JUnit provides a lifecycle hooks for running tests. Each test goes through several phases or stages during its execution. After learning these lifecycle methods, you will have better control and flexibility on the unit tests you write.
The JUnit lifecycle can be divided into three main phases: the setup phase, the test execution phase, and the teardown phase.
1. Setup Phase:
The setup phase is responsible for preparing the environment required for running a test. It includes initializing variables, setting up mock objects or test doubles, and any necessary configuration.
To handle the setup phase, JUnit offers two annotations: @BeforeAll and @BeforeEach. The @BeforeAll annotation is used at the class level to define methods that will be executed before each test case in that class. On the other hand, the @BeforeEach annotation is used at the method level to define methods that will be executed before each individual test methods.
Let’s see an example of using @BeforeEach annotation:
1import org.junit.jupiter.api.BeforeEach;
2
3public class DemoTestClass {
4
5 @BeforeEach
6 public void setup() {
7 // Perform setup actions here
8 }
9
10 // Test methods go here...
11}
@BeforeAll: This method is annotated with@BeforeAlland is executed once for the entire test class before any test methods are run. It’s typically used for one-time setup tasks such as initializing resources that will be shared across multiple tests.@BeforeEach: This method is annotated with@BeforeEachand is executed before each individual test method. It’s used to set up the preconditions required for a particular test case, such as creating objects or initializing variables.
2. Test Execution Phase:
The test execution phase is where the actual testing takes place. This is where you write your assertions and validate expected outcomes against actual results.
By default, all public methods annotated with @Test are considered as test cases by JUnit and will be executed during this phase.
1import org.junit.jupiter.api.Test;
2import static org.junit.jupiter.api.Assertions.assertEquals;
3
4public class CalculatorTest {
5
6 @Test
7 public void testAdd() {
8 int result = Calculator.add(2, 3);
9 assertEquals(5, result);
10 }
11
12 // Additional test methods go here...
13}
3. Teardown Phase:
The teardown phase is responsible for cleaning up resources and releasing any objects created during the test execution. This phase ensures that each test starts with a clean state, making tests independent of each other.
JUnit provides two annotations to handle the teardown phase: @After and @AfterEach. The @After annotation is used at the class level, similar to @Before, to define methods that will be executed after all test cases in that class. The @AfterEach annotation, at the method level, defines methods that will be executed after each individual test method.
Here’s an example using @AfterEach annotation:
1import org.junit.jupiter.api.AfterEach;
2
3public class MyTestClass {
4
5 @AfterEach
6 public void tearDown() {
7 // Clean up resources here
8 }
9
10 // Test methods go here...
11}
@AfterEach: This method is annotated with@AfterEach, and it’s executed after each individual test method completes execution. It’s used for cleanup tasks, such as releasing resources or resetting state, after a specific test case.@AfterAll: This method is annotated with@AfterAlland is executed once for the entire test class after all the test methods have been run. It’s typically used for tear-down tasks like closing connections, cleaning up resources, or performing any post-testing activities.
Lifecycle Methods Example
Below code has two test methods defined with all lifecycle methods. Please note that @BeforeAll and @AfterAll methods have to be static methods. They are defined at class level and are executed only once when we execute this test class.
1import org.junit.jupiter.api.*;
2
3public class LifeCycleTests {
4 @BeforeAll
5 static void setUp() {
6 System.out.println("BeforeAll Method");
7 }
8
9 @BeforeEach
10 void beforeEach() {
11 System.out.println("BeforeEach Method");
12 }
13
14 @Test
15 void testA() {
16 System.out.println("Testing A");
17 }
18
19 @Test
20 void testB() {
21 System.out.println("Testing B");
22 }
23
24 @AfterEach
25 void afterEach() {
26 System.out.println("AfterEach Method");
27 }
28
29 @AfterAll
30 static void tearDown() {
31 System.out.println("AfterAll Method");
32 }
33}
Output:
1BeforeAll Method
2BeforeEach Method
3Testing A
4AfterEach Method
5BeforeEach Method
6Testing B
7AfterEach Method
8AfterAll Method
As you can see, the method annotated with @BeforeAll gets executed once and at the very beginning of the test execution. Contrary to this, @AfterAll method gets executed at the end of the test class. The method names can be anything, but conventionally, they are called setUp and tearDown methods. Similarly, @BeforeEach method gets executed before each test method (the methods annotated with @Test) and @AfterEach methods will be executed after each test method.
Practical Usage
In our previously defined CalculatorTest class, we were creating new Calculator instance in each of our test methods.
1class CalculatorTest {
2 @Test
3 @DisplayName("Test Positive Numbers Addition")
4 void testAdd() {
5 Calculator calculator = new Calculator();
6 assertEquals(2, calculator.add(1, 1));
7 }
8
9 @Test
10 @DisplayName("Test Negative Numbers Addition")
11 void testAddNegativeNumbers() {
12 Calculator calculator = new Calculator();
13 assertEquals(-5, calculator.add(-2, -3));
14 }
15}
We can use @BeforeEach method to create this new Calculator instance before each method. This way we will not need to duplicate the same lines in each method.
1class CalculatorTest {
2 Calculator calculator;
3
4 @BeforeEach
5 void setUp() {
6 calculator = new Calculator();
7 }
8
9 @Test
10 @DisplayName("Test Positive Numbers Addition")
11 void testAdd() {
12 assertEquals(2, calculator.add(1, 1));
13 }
14
15 @Test
16 @DisplayName("Test Negative Numbers Addition")
17 void testAddNegativeNumbers() {
18 assertEquals(-5, calculator.add(-2, -3));
19 }
20}
After using all these knowledge we have gained, we can modify our calculator application to use these lifecycle methods. Below is the application code. I have also modified divide() method to throw IllegalArgumentException if the denominator is zero.
1public class Calculator {
2 public int add(int num1, int num2) {
3 return num1 + num2;
4 }
5
6 public int subtract(int num1, int num2) {
7 return num1 - num2;
8 }
9
10 public int multiply(int num1, int num2) {
11 return num1 * num2;
12 }
13
14 public int divide(int num1, int num2) {
15 if (num2 == 0)
16 throw new IllegalArgumentException(
17 String.format("%d is not a valid denominator.", num2)
18 );
19 return num1 / num2;
20 }
21}
By using @BeforeEach, we can wire the Calculator instance that can be used in each of the test cases.
1import org.junit.jupiter.api.BeforeEach;
2import org.junit.jupiter.api.DisplayName;
3import org.junit.jupiter.api.Test;
4
5import static org.junit.jupiter.api.Assertions.*;
6
7@DisplayName("Calculator Test")
8class CalculatorTest {
9 Calculator calculator;
10
11 @BeforeEach
12 void setUp() {
13 calculator = new Calculator();
14 }
15
16 @Test
17 @DisplayName("Test Positive Numbers Addition")
18 void testAdd() {
19 assertEquals(2, calculator.add(1, 1));
20 }
21
22 @Test
23 @DisplayName("Test Negative Numbers Addition")
24 void testAddNegativeNumbers() {
25 assertEquals(-5, calculator.add(-2, -3));
26 }
27
28 @Test
29 @DisplayName("Test Subtraction")
30 void testSubtraction() {
31 assertEquals(1, calculator.subtract(2, 1));
32 assertEquals(-5, calculator.subtract(5, 10));
33 assertEquals(-5, calculator.subtract(-2, 3));
34 }
35
36 @Test
37 @DisplayName("Test Multiplication")
38 void testMultiply() {
39 assertEquals(15, calculator.multiply(5, 3));
40 assertEquals(0, calculator.multiply(0, 10));
41 }
42
43 @Test
44 @DisplayName("Test Division")
45 void testDivision() {
46 assertEquals(1, calculator.divide(3, 2));
47 assertEquals(2, calculator.divide(4, 2));
48 assertEquals(2, calculator.divide(12, 5));
49 assertThrows(
50 IllegalArgumentException.class,
51 () -> calculator.divide(100, 0)
52 );
53 assertDoesNotThrow(() -> calculator.divide(0, 100));
54 }
55}


Comments