Nested Tests
@Nested tests give the test writer more capabilities to express the relationship among
several groups of tests. Such nested tests make use of Java’s nested classes and
facilitate hierarchical thinking about the test structure. Here’s an elaborate example,
both as source code and as a screenshot of the execution within an IDE.
-
Java
-
Kotlin
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.EmptyStackException;
import java.util.Stack;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@DisplayName("A stack")
class TestingAStackDemo {
@Test
@DisplayName("is instantiated with new Stack()")
void isInstantiatedWithNew() {
new Stack<>();
}
@Nested
@DisplayName("when new")
class WhenNew {
Stack<Object> stack;
@BeforeEach
void createNewStack() {
stack = new Stack<>();
}
@Test
@DisplayName("is empty")
void isEmpty() {
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("throws EmptyStackException when popped")
void throwsExceptionWhenPopped() {
assertThrows(EmptyStackException.class, stack::pop);
}
@Test
@DisplayName("throws EmptyStackException when peeked")
void throwsExceptionWhenPeeked() {
assertThrows(EmptyStackException.class, stack::peek);
}
@Nested
@DisplayName("after pushing an element")
class AfterPushing {
String anElement = "an element";
@BeforeEach
void pushAnElement() {
stack.push(anElement);
}
@Test
@DisplayName("it is no longer empty")
void isNotEmpty() {
assertFalse(stack.isEmpty());
}
@Test
@DisplayName("returns the element when popped and is empty")
void returnElementWhenPopped() {
assertEquals(anElement, stack.pop());
assertTrue(stack.isEmpty());
}
@Test
@DisplayName("returns the element when peeked but remains not empty")
void returnElementWhenPeeked() {
assertEquals(anElement, stack.peek());
assertFalse(stack.isEmpty());
}
}
}
}
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import java.util.EmptyStackException
import java.util.Stack
@DisplayName("A stack")
class TestingAStackDemo {
@Test
@DisplayName("is instantiated with new Stack()")
fun isInstantiatedWithNew() {
Stack<Any>()
}
@Nested
@DisplayName("when new")
inner class WhenNew {
lateinit var stack: Stack<Any>
@BeforeEach
fun createNewStack() {
stack = Stack()
}
@Test
@DisplayName("is empty")
fun isEmpty() {
assertTrue(stack.isEmpty())
}
@Test
@DisplayName("throws EmptyStackException when popped")
fun throwsExceptionWhenPopped() {
assertThrows<EmptyStackException> { stack.pop() }
}
@Test
@DisplayName("throws EmptyStackException when peeked")
fun throwsExceptionWhenPeeked() {
assertThrows<EmptyStackException> { stack.peek() }
}
@Nested
@DisplayName("after pushing an element")
inner class AfterPushing {
val anElement = "an element"
@BeforeEach
fun pushAnElement() {
stack.push(anElement)
}
@Test
@DisplayName("it is no longer empty")
fun isNotEmpty() {
assertFalse(stack.isEmpty())
}
@Test
@DisplayName("returns the element when popped and is empty")
fun returnElementWhenPopped() {
assertEquals(anElement, stack.pop())
assertTrue(stack.isEmpty())
}
@Test
@DisplayName("returns the element when peeked but remains not empty")
fun returnElementWhenPeeked() {
assertEquals(anElement, stack.peek())
assertFalse(stack.isEmpty())
}
}
}
}
When executing this example in an IDE, the test execution tree in the GUI will look similar to the following image.
In this example, preconditions from outer tests are used in inner tests by defining
hierarchical lifecycle methods for the setup code. For example, createNewStack() is a
@BeforeEach lifecycle method that is used in the test class in which it is defined and
in all levels in the nesting tree below the class in which it is defined.
The fact that setup code from outer tests is run before inner tests are executed gives you the ability to run all tests independently. You can even run inner tests alone without running the outer tests, because the setup code from the outer tests is always executed.
Only non-static nested classes (i.e. inner classes) can serve as @Nested test
classes. Nesting can be arbitrarily deep, and those inner classes are subject to full
lifecycle support, including @BeforeAll and @AfterAll methods on each level.
|
Interoperability
@Nested may be combined with
@ParameterizedClass in which case the nested test
class is parameterized.
The following example illustrates how to combine @Nested with @ParameterizedClass and
@ParameterizedTest.
-
Java
-
Kotlin
@Execution(SAME_THREAD)
@ParameterizedClass
@ValueSource(strings = { "apple", "banana" })
class FruitTests {
@Parameter
String fruit;
@Nested
@ParameterizedClass
@ValueSource(ints = { 23, 42 })
class QuantityTests {
@Parameter
int quantity;
@ParameterizedTest
@ValueSource(strings = { "PT1H", "PT2H" })
void test(Duration duration) {
assertFruit(fruit);
assertQuantity(quantity);
assertFalse(duration.isNegative());
}
}
}
@Execution(SAME_THREAD)
@ParameterizedClass
@ValueSource(strings = ["apple", "banana"])
inner class FruitTests {
@Parameter
lateinit var fruit: String
@Nested
@ParameterizedClass
@ValueSource(ints = [23, 42])
inner class QuantityTests {
@Parameter
var quantity: Int = 0
@ParameterizedTest
@ValueSource(strings = ["PT1H", "PT2H"])
fun test(duration: Duration) {
assertFruit(fruit)
assertQuantity(quantity)
assertFalse(duration.isNegative)
}
}
}
Executing the above test class yields the following output:
FruitTests ✔
├─ [1] fruit = "apple" ✔
│ └─ QuantityTests ✔
│ ├─ [1] quantity = 23 ✔
│ │ └─ test(Duration) ✔
│ │ ├─ [1] duration = "PT1H" ✔
│ │ └─ [2] duration = "PT2H" ✔
│ └─ [2] quantity = 42 ✔
│ └─ test(Duration) ✔
│ ├─ [1] duration = "PT1H" ✔
│ └─ [2] duration = "PT2H" ✔
└─ [2] fruit = "banana" ✔
└─ QuantityTests ✔
├─ [1] quantity = 23 ✔
│ └─ test(Duration) ✔
│ ├─ [1] duration = "PT1H" ✔
│ └─ [2] duration = "PT2H" ✔
└─ [2] quantity = 42 ✔
└─ test(Duration) ✔
├─ [1] duration = "PT1H" ✔
└─ [2] duration = "PT2H" ✔