Writing Nested Tests With JUnit 5
After we have finished this webinar, we:
- Can write nested tests with JUnit 5.
- Understand how we can use setup and teardown methods.
This webinar assumes that:
Introduction to Our JUnit 5 Test Class
The previous webinar described how we can use setup and teardown methods, and add test methods to our test classes. Also, we wrote a really simple test class and added all setup and teardown methods to the created class. During this webinar we will use the same test class and add nested setup, teardown, and test methods to that class.
The source code of our test class looks as follows:
1import org.junit.jupiter.api.*;
2
3@DisplayName("JUnit 5 Nested Example")
4class JUnit5NestedExampleTest {
5
6 @BeforeAll
7 static void beforeAll() {
8 System.out.println("Before all test methods");
9 }
10
11 @BeforeEach
12 void beforeEach() {
13 System.out.println("Before each test method");
14 }
15
16 @AfterEach
17 void afterEach() {
18 System.out.println("After each test method");
19 }
20
21 @AfterAll
22 static void afterAll() {
23 System.out.println("After all test methods");
24 }
25}
Let’s move on and find out how we can write nested tests with JUnit 5.
Writing Nested Tests
Let’s add a few inner classes to our test class. The idea of this exercise is to demonstrate the invocation order of setup, teardown, and test methods. We can add the required inner classes to our test class by following these steps:
First, we have to add a new inner class called A to our test class and annotate the inner class with the @Nested annotation. This annotation ensures that JUnit 5 recognizes our nested test class. After we have created the A class, we have to add one setup, teardown, and test method to the created inner class.
After we have added this inner class to the JUnit5NestedExampleTest class, the source code of our test class looks as follows:
1import org.junit.jupiter.api.*;
2
3@DisplayName("JUnit 5 Nested Example")
4class JUnit5NestedExampleTest {
5
6 @BeforeAll
7 static void beforeAll() {
8 System.out.println("Before all test methods");
9 }
10
11 @BeforeEach
12 void beforeEach() {
13 System.out.println("Before each test method");
14 }
15
16 @AfterEach
17 void afterEach() {
18 System.out.println("After each test method");
19 }
20
21 @AfterAll
22 static void afterAll() {
23 System.out.println("After all test methods");
24 }
25
26 @Nested
27 @DisplayName("Tests for the method A")
28 class A {
29
30 @BeforeEach
31 void beforeEach() {
32 System.out.println("Before each test method of the A class");
33 }
34
35 @AfterEach
36 void afterEach() {
37 System.out.println("After each test method of the A class");
38 }
39
40 @Test
41 @DisplayName("Sample test for method A")
42 void sampleTestForMethodA() {
43 System.out.println("Sample test for method A");
44 }
45 }
46}
There are three things I want to point out before we can continue this webinar:
- If we want to add nested test classes to the “root” class, we have to use non-static inner classes.
- There is no limit for the depth of the class hierarchy.
- Because Java doesn’t allow
staticmembers in inner classes, the@BeforeAlland@AfterAllmethods don’t work by default. If you want to use them, you should take a look at the JUnit 5 User Guide .
Second, we have to add a new inner class called WhenX to the A class and annotate the inner class with the @Nested annotation. After we have created the WhenX class, we have to add one setup, teardown, and test method to the created inner class.
After we have added this inner class to the A class, the source code of our test class looks as follows:
1import org.junit.jupiter.api.*;
2
3@DisplayName("JUnit 5 Nested Example")
4class JUnit5NestedExampleTest {
5
6 @BeforeAll
7 static void beforeAll() {
8 System.out.println("Before all test methods");
9 }
10
11 @BeforeEach
12 void beforeEach() {
13 System.out.println("Before each test method");
14 }
15
16 @AfterEach
17 void afterEach() {
18 System.out.println("After each test method");
19 }
20
21 @AfterAll
22 static void afterAll() {
23 System.out.println("After all test methods");
24 }
25
26 @Nested
27 @DisplayName("Tests for the method A")
28 class A {
29
30 @BeforeEach
31 void beforeEach() {
32 System.out.println("Before each test method of the A class");
33 }
34
35 @AfterEach
36 void afterEach() {
37 System.out.println("After each test method of the A class");
38 }
39
40 @Test
41 @DisplayName("Sample test for method A")
42 void sampleTestForMethodA() {
43 System.out.println("Sample test for method A");
44 }
45
46 @Nested
47 @DisplayName("When X is true")
48 class WhenX {
49
50 @BeforeEach
51 void beforeEach() {
52 System.out.println("Before each test method of the WhenX class");
53 }
54
55 @AfterEach
56 void afterEach() {
57 System.out.println("After each test method of the WhenX class");
58 }
59
60 @Test
61 @DisplayName("Sample test for method A when X is true")
62 void sampleTestForMethodAWhenX() {
63 System.out.println("Sample test for method A when X is true");
64 }
65 }
66 }
67}
We have now created a simple test class hierarchy. It’s no secret that the key of writing clean nested tests which are easy read, write, and maintain is to build our class hierarchy in a way that helps us to reduce duplicate code. For example:
- If we are writing unit tests, one test class should contain all test cases of one class. Also, the inner classes found from the first level of the class hierarchy should contain the test cases of the methods of the tested class. When we want to test different scenarios, we can add new inner classes to the inner classes that contain the test cases of the tested method.
- If we are writing integration or end-to-end tests, one test class should contain all test cases of one feature. This means that we can test different scenarios by adding inner classes directly to the root class.
Also, creating a logical test class hierarchy is not good enough. We have to also use names and descriptions that help us to understand the logic behind our test class hierarchy.
Additional Reading:
Enough with theory. Let’s see what happens when we run our unit tests.
Running Our Unit Tests
When we run our unit tests, we should see the following output:
1Before all test methods
2Before each test method
3Before each test method of the A class
4Sample test for method A
5After each test method of the A class
6After each test method
7Before each test method
8Before each test method of the A class
9Before each test method of the WhenX class
10Sample test for method A when X is true
11After each test method of the WhenX class
12After each test method of the A class
13After each test method
14After all test methods
In other words, JUnit 5 invokes the setup and teardown methods by following the context hierarchy of the invoked test method. This means that we can eliminate duplicate code by putting our code to the correct place. If you want learn more about this, you should take a look at the lesson: The “Best Practices” of Nested Unit Tests .
Let’s summarize what we learned from this webinar.
Summary
This webinar has taught us five things:
- All nested test classes must be non-static.
- All nested test classes must be annotated with the
@Nestedannotation. - The depth of the test class hierarchy is not limited in any way.
- We can add
@BeforeEachand@AfterEachmethods to nested test classes. - By default, we cannot add
@BeforeAlland@AfterAllmethods to our nested test classes.