Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

In this short tutorial, we’ll discuss JUnit 5’s @Nested annotation. We’ll start by looking at a trivial example and understanding how nested test classes are run. After that, we’ll learn how to take advantage of the new feature for a more production-like use case.

2. How @Nested Works

We can use JUnit5’s @Nested annotation to create nested test classes. The annotation must be added at a class level, for each inner class that contains tests:

public class NestedTest {
    @Nested
    class FirstNestedClass {
        @Test
        void test() {
            System.out.println("FirstNestedClass.test()");
        }
    }

    @Nested
    class SecondNestedClass {
        @Test
        void test() {
            System.out.println("SecondNestedClass.test()");
        }
    }
}

As a result, we can run the parent test class and all the tests from the nested test classes should be executed. Most of the IDEs will show a nice hierarchical representation of the tests:

 

nested tests intellij

Additionally, if we add setup or tear-down methods, they will be executed in their declaration. For instance, if we add a @BeforeEach method for each of the three classes, we’ll expect each test to execute the setup method of  the parent class, then the one from its own class, and, after that, the test itself:

public class NestedTest {
    @BeforeEach()
    void beforeEach() {
        System.out.println("NestedTest.beforeEach()");
    }

    @Nested
    class FirstNestedClass {
        @BeforeEach()
        void beforeEach() {
            System.out.println("FirstNestedClass.beforeEach()");
        }

        @Test
        void test() {
            System.out.println("FirstNestedClass.test()");
        }
    }

    @Nested
    class SecondNestedClass {
        @BeforeEach()
        void beforeEach() {
            System.out.println("SecondNestedClass.beforeEach()");
        }

        @Test
        void test() {
            System.out.println("SecondNestedClass.test()");
        }
   }
}

Let’s run the test class and check the order of the print statements in the console:

 

nested tests execution

As expected, both tests are using the common setup defined by the parent class. After that, they run the setup method from their own class, and then they execute the test. Moreover, the other setup and tear-down methods: @BeforeAll, @AfterEach, and @AfterAll follow the same pattern.

3. When To Use @Nested

If we have setup or tear-down methods that repeat for some of the tests, but not for all of them, a @Nested test class can be very useful.

Furthermore, using nested classes for setting up groups of tests lead to more expressive test scenarios and a clear relationship between our tests.

3.1. Reusing the Test Scenario

Let’s use the @Nested annotation to write some tests for a more complex use case.

Let’s assume we are testing the backend application of an online publication. The clients of this publication can have one of the three types of membership:

public enum Membership {
    FREE, SILVER, GOLD;
}

Based on their membership, the users can either open and read the articles or see them as locked.

Let’s start by creating a Publication and three Article objects:

class OnlinePublicationTest {
    private Publication publication;

    @BeforeEach
    void setupArticlesAndPublication() {
        Article freeArticle = new Article("free article", Membership.FREE);
        Article silverArticle = new Article("silver level article", Membership.SILVER);
        Article goldArticle = new Article("gold level article", Membership.GOLD);
        publication = new Publication(Arrays.asList(freeArticle, silverArticle, goldArticle));
    }

    @Test
    void shouldHaveThreeArticlesInTotal() {
        List<Article> allArticles = publication.getArticles();
        assertThat(allArticles).hasSize(3);
    }
}

Now, let’s assume a user with free membership checks the publication. We’ll expect him to be able to read only one article and see the other two as ‘locked’. Let’s write this as two different unit tests, in a nested test class:

@Nested
class UserWithoutMembership {
    User freeFreya = new User("Freya", Membership.FREE);

    @Test
    void shouldOnlyReadFreeArticles() {
        List<String> articles = publication.getReadableArticles(freeFreya);
        assertThat(articles).containsExactly("free article");
    }

    @Test
    void shouldSeeSilverAndGoldLevelArticlesAsLocked() {
        List<String> articles = publication.getLockedArticles(freeFreya);
        assertThat(articles).containsExactlyInAnyOrder("silver level article", "gold level article");
    }
}

Now, let’s run similar test classes for ‘silver’ and ‘gold’ membership users and run the whole class:

 

online publication test scenario

3.2. Using Nested Test With @DisplayName

It’s worth noting how the name of the classes, together with the name of the tests, are describing the whole scenario under test. Similarly, we can annotate our test classes and methods with @DisaplyName to further customize the way they are shown. For instance, we can use the given-when-then pattern:

 

nested test with display name

4. Conclusion

In this short article, we learned how JUnit5’s @Nested annotation works and how it can be used to create expressive test scenarios. We saw that we can create a nested test class when the setup or the tear-down of multiple tests is very similar, but it doesn’t apply to all the tests from the class.

As always, the full source code for this article is available over on GitHub.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Comments are open for 30 days after publishing a post. For any issues past this date, use the Contact form on the site.