I released five new sample lessons from my Test With Spring course: Introduction to Spock Framework

JUnit 5 Tutorial: Writing Nested Tests

This blog post describes how we can write nested tests with JUnit 5. To be more specific, we will learn to create nested test classes, and leverage setup and teardown methods in our nested tests.

Let’s start by taking a look at our test class.

This blog post assumes that:

Introduction to Our Test Class

The previous part of this tutorial described how we can use setup and teardown methods, and add test methods to our test classes. Also, we wrote a simple test class, and added all setup and teardown methods to the created class.

The source code of our test class looks as follows:

import org.junit.jupiter.api.*;

@DisplayName("JUnit 5 Nested Example")
class JUnit5NestedExampleTest {

    @BeforeAll
    static void beforeAll() {
        System.out.println("Before all test methods");
    }

    @BeforeEach
    void beforeEach() {
        System.out.println("Before each test method");
    }

    @AfterEach
    void afterEach() {
        System.out.println("After each test method");
    }

    @AfterAll
    static void afterAll() {
        System.out.println("After all test methods");
    }
}

Next, we will add nested setup, teardown, and test methods to our test class.

Writing Nested Tests

When we write nested tests with JUnit 5, we will basically create a nested test class hierarchy that contains our setup, teardown, and test methods. When we add nested test classes to our test class, we have to follow these rules:

  • All nested test classes must be non-static inner classes.
  • We have annotate our nested test classes with the @Nested annotation. This annotation ensures that JUnit 5 recognizes our nested test classes.
  • There is no limit for the depth of the class hierarchy.
  • By default, a nested test class can contain test methods, one @BeforeEach method, and one @AfterEach method.
  • Because Java doesn’t allow static members in inner classes, the @BeforeAll and @AfterAll methods don’t work by default.
If you want to use the @BeforeAll and @AfterAll methods, you should take a look at the JUnit 5 User Guide.

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. 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:

import org.junit.jupiter.api.*;

@DisplayName("JUnit 5 Nested Example")
class JUnit5NestedExampleTest {

    @BeforeAll
    static void beforeAll() {
        System.out.println("Before all test methods");
    }

    @BeforeEach
    void beforeEach() {
        System.out.println("Before each test method");
    }

    @AfterEach
    void afterEach() {
        System.out.println("After each test method");
    }

    @AfterAll
    static void afterAll() {
        System.out.println("After all test methods");
    }

    @Nested
    @DisplayName("Tests for the method A")
    class A {

        @BeforeEach
        void beforeEach() {
            System.out.println("Before each test method of the A class");
        }

        @AfterEach
        void afterEach() {
            System.out.println("After each test method of the A class");
        }

        @Test
        @DisplayName("Example test for method A")
        void sampleTestForMethodA() {
            System.out.println("Example test for method A");
        }
    }
}

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:

import org.junit.jupiter.api.*;

@DisplayName("JUnit 5 Nested Example")
class JUnit5NestedExampleTest {

    @BeforeAll
    static void beforeAll() {
        System.out.println("Before all test methods");
    }

    @BeforeEach
    void beforeEach() {
        System.out.println("Before each test method");
    }

    @AfterEach
    void afterEach() {
        System.out.println("After each test method");
    }

    @AfterAll
    static void afterAll() {
        System.out.println("After all test methods");
    }

    @Nested
    @DisplayName("Tests for the method A")
    class A {

        @BeforeEach
        void beforeEach() {
            System.out.println("Before each test method of the A class");
        }

        @AfterEach
        void afterEach() {
            System.out.println("After each test method of the A class");
        }

        @Test
        @DisplayName("Example test for method A")
        void sampleTestForMethodA() {
            System.out.println("Example test for method A");
        }

        @Nested
        @DisplayName("When X is true")
        class WhenX {

            @BeforeEach
            void beforeEach() {
                System.out.println("Before each test method of the WhenX class");
            }

            @AfterEach
            void afterEach() {
                System.out.println("After each test method of the WhenX class");
            }

            @Test
            @DisplayName("Example test for method A when X is true")
            void sampleTestForMethodAWhenX() {
                System.out.println("Example test for method A when X is true");
            }
        }
    }
}

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:

Before all test methods
Before each test method
Before each test method of the A class
Example test for method A
After each test method of the A class
After each test method
Before each test method
Before each test method of the A class
Before each test method of the WhenX class
Example test for method A when X is true
After each test method of the WhenX class
After each test method of the A class
After each test method
After 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 to learn how to put your code to the correct place, you should take a look at this sample lesson of my Test With Spring course: The “Best Practices” of Nested Unit Tests.

Let’s summarize what we learned from this blog post.

Summary

This blog post has taught us five things:

  • All nested test classes must be non-static.
  • All nested test classes must be annotated with the @Nested annotation.
  • The depth of the test class hierarchy is not limited in any way.
  • A nested test class can contain test methods, one @BeforeEach method, and one @AfterEach method.
  • By default, we cannot add the @BeforeAll and @AfterAll methods to a nested test class.

P.S. You can get the example application of this blog post from Github.

About the Author

Petri Kainulainen is passionate about software development and continuous improvement. He is specialized in software development with the Spring Framework and is the author of Spring Data book.

About Petri Kainulainen →

2 comments… add one
  • Nested tests are very useful because our test cases are often not a list, but form a tree. This leads to maintenance issues and duplication when we try to fit that tree in a list of methods annotated with @Test :)

    One more thing – I have studied JUnit 5 Guide and totally missed the @TestInstance(Lifecycle.PER_CLASS) as a way to make @BeforeAll/@AfterAll possible on non-static methods. Of course that comes with other limitations, but still – great tip!

    Reply

Leave a Comment