Nested tests

Nested tests give the test writer more capabilities to express the relationship and order in a group of tests. JUnit 5 makes it effortless to nest test classes. We simply need to annotate inner classes with @Nested and all test methods in there will be executed as well, going from the regular tests (defined in the top-level class) to the tests defined in each of the inner classes.

The first thing we need to take into account is that only non-static nested classes (that is inner classes) can serve as @Nested tests. Nesting can be arbitrarily deep, and the setup and tear down for each test (that is, @BeforeEach and @AfterEach methods) are inherited in the nested tests. Nevertheless, inner classes cannot define the @BeforeAll and @AfterAll methods, due to the fact that Java does not allow static members in inner classes. However, this restriction can be avoided using the annotation @TestInstance(Lifecycle.PER_CLASS) in the test class. As described in the section Test instance lifecycle in this chapter, this annotation force to instance a test instance per class, instead of a test instance per method (default behavior). This way, the methods @BeforeAll and @AfterAll do not need to be static and therefore it can be used in nested tests.

Let's see a simple example composed by a Java class with two levels of inner classes, that is, the class contains two nested inner classes annotated with @Nested. As we can see, there are tests in the three levels of the class. Notice that the top class defined a setup method (@BeforeEach), and also the first nested class (called InnerClass1 in the example). In the top-level class, we define a single test (called topTest), and in each nested class we find another test (called innerTest1 and innerTest2, respectively):

package io.github.bonigarcia;

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

class NestTest {

@BeforeEach
void setup1() {
System.out.println("Setup 1");
}

@Test
void topTest() {
System.out.println("Test 1");
}

@Nested
class InnerClass1 {

@BeforeEach
void setup2() {
System.out.println("Setup 2");
}

@Test
void innerTest1() {
System.out.println("Test 2");
}

@Nested
class InnerClass2 {

@Test
void innerTest2() {
System.out.println("Test 3");
}
}
}

}

If we execute this example, we can trace the execution of the nested tests by simply looking to the console traces. Note that the top @BeforeEach method (called setup1) is always executed before each test. Therefore, the trace Setup 1 is always present in the console before the actual test execution. Each test also writes a line the console. As we can see, the first test logs Test 1. After that, the tests defined in the inner classes are executed. The first inner class executes the test innerTest1, but after that, the setup method of the top-level class and the first inner class are executed (logging Setup 1 and Setup 2, respectively).

Finally, the test defined in the last inner class (innerTest2) is executed, but as usual, the cascade of setup methods is executed before the test:

Console output of the execution of the nested test example

Nested tests can be used in conjunction with the display name (that is, the annotation @DisplayName) to help to produce a nicely readable test output. The following example demonstrates how. This class contains the structure to test the implementation of a stack, that is, a last-in-first-out (LIFO) collection. The class is designed to first test the stack when it is just instantiated (the method isInstantiatedWithNew). After that, the first inner class (WhenNew) is supposed to test the stack as an empty collection (methods isEmpty, throwsExceptionWhenPopped and throwsExceptionWhenPeeked). Finally, the second inner class is supposed to test when the stack is not empty (methods isNotEmpty, returnElementWhenPopped, and returnElementWhenPeeked):

package io.github.bonigarcia;

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

@DisplayName("A stack test")

class StackTest {

@Test
@DisplayName("is instantiated")
void isInstantiated() {
}

@Nested
@DisplayName("when empty")
class WhenNew {

@Test
@DisplayName("is empty")
void isEmpty() {
}

@Test
@DisplayName("throws Exception when popped")
void throwsExceptionWhenPopped() {
}

@Test
@DisplayName("throws Exception when peeked")
void throwsExceptionWhenPeeked() {
}

@Nested
@DisplayName("after pushing an element")
class AfterPushing {

@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
}

@Test
@DisplayName("returns the element when popped")
void returnElementWhenPopped() {
}

@Test
@DisplayName("returns the element when peeked")
void returnElementWhenPeeked() {
}

}
}
}

The objective of this type of test is two folded. On the one hand, the class structure provides an order for the execution of the tests. On the other hand, the use of @DisplayName improves the readability of the test execution. We can see that when the test is executed in an IDE, concretely in IntelliJ IDEA.

Execution of nested test using @DisplayName on Intellij IDEA
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.20.231