Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Readable names and nested classes

As the number of tests grows, a flat list of methods in one class becomes hard to read. JUnit offers two tools. @DisplayName gives a test a human-readable name with spaces and any characters. @Nested groups tests into inner classes by situation: "when creating a post", "when publishing a post". Together they turn a test report into a description of how the class behaves.

@DisplayName

// src/test/java/com/example/blog/PostServiceNestedTest.java
@Test
@DisplayName("rejects an empty title")
void rejectsEmptyTitle() {
    assertThrows(IllegalArgumentException.class, () -> service.create(" ", "anna"));
}

The IDE shows "rejects an empty title" instead of the method name. The method name still matters: Maven uses it in reports, and you see it in stack traces.

@Nested

A nested test class is a non-static inner class with the @Nested annotation. Here is the whole test class:

// src/test/java/com/example/blog/PostServiceNestedTest.java
package com.example.blog;
 
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 static org.junit.jupiter.api.Assertions.*;
 
@DisplayName("PostService")
class PostServiceNestedTest {
 
    private PostService service;
 
    @BeforeEach
    void setUp() {
        service = new PostService(new InMemoryPostRepository(), (to, message) -> { });
    }
 
    @Nested
    @DisplayName("when creating a post")
    class Create {
 
        @Test
        @DisplayName("trims spaces in the title")
        void trimsTitle() {
            assertEquals("Hello", service.create("  Hello ", "anna").title());
        }
 
        @Test
        @DisplayName("rejects an empty title")
        void rejectsEmptyTitle() {
            assertThrows(IllegalArgumentException.class, () -> service.create(" ", "anna"));
        }
    }
 
    @Nested
    @DisplayName("when publishing a post")
    class Publish {
 
        private Post draft;
 
        @BeforeEach
        void createDraft() {
            draft = service.create("Hello", "anna");
        }
 
        @Test
        @DisplayName("marks it as published")
        void marksPublished() {
            assertTrue(service.publish(draft.id()).published());
        }
 
        @Test
        @DisplayName("fails for an unknown id")
        void unknownId() {
            assertThrows(PostNotFoundException.class, () -> service.publish(99));
        }
    }
}

Before a test in Publish, JUnit first calls the outer setUp(), then the inner createDraft(). So each group adds its own preparation on top of the shared one. The notifier here is an empty lambda: these tests do not check notifications.

What you see

In IntelliJ IDEA the run window shows a tree:

PostService
├─ when creating a post
│  ├─ trims spaces in the title
│  └─ rejects an empty title
└─ when publishing a post
   ├─ marks it as published
   └─ fails for an unknown id

Maven prints the display names of the groups:

[INFO] Running PostService
[INFO] Running when creating a post
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.011 s -- in when creating a post
[INFO] Running when publishing a post
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.010 s -- in when publishing a post
[INFO] Tests run: 0, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.026 s -- in PostService

The outer class itself has no tests, hence Tests run: 0 on the last line — the four tests are counted in the groups.

When to use it

Nested classes are handy when a class has several methods, each with its own set of cases, or when tests share a common starting state ("there is a draft"). For a class with three tests a flat list is simpler.

Common mistake

The inner class must not be static. A static nested class with @Nested is not run inside the outer class, and Surefire does not run it on its own either:

WARNING: TestEngine with ID 'junit-jupiter' encountered a non-critical issue during test discovery:
 
(1) [WARNING] @Nested class 'com.example.blog.StaticNestedTest$Create' must not be static. It will only be executed if discovered as a standalone test class. You should remove the annotation or make it non-static to resolve this warning.
...
[INFO] Tests run: 0, Failures: 0, Errors: 0, Skipped: 0

Remove static — then the class gets access to the fields of the outer class, such as service.

Thanks for reading!

Donate to the site — we work hard for you.