Generic Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Overview

JUnit 5 has good support for customizing test class and test method names. In this quick tutorial, we'll see how we can use JUnit 5 custom display name generators via the @DisplayNameGeneration annotation.

2. Display Name Generation

We can configure custom display name generators via the @DisplayNameGeneration annotation. However, it's good to be aware that the @DisplayName annotation always takes precedence over any display name generator.

To start with, JUnit 5 provides a DisplayNameGenerator.ReplaceUnderscores class that replaces any underscores in names with spaces. Let's take a look at an example:

@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class ReplaceUnderscoresGeneratorUnitTest {

    @Nested
    class when_doing_something {

        @Test
        void then_something_should_happen() {
        }

        @Test
        @DisplayName("@DisplayName takes precedence over generation")
        void override_generator() {
        }
    }
}

Now, when we run the test we can see the display name generation made the test output more readable:

└─ ReplaceUnderscoresGeneratorUnitTest ✓
   └─ when doing something ✓
      ├─ then something should happen() ✓
      └─ @DisplayName takes precedence over generation ✓

3. Custom Display Name Generator

In order to write a custom display name generator, we have to write a class that implements the methods in the DisplayNameGenerator interface. The interface has methods for generating the name for a class, a nested class, and a method.

3.1. Camel Case Replacement

Let's start with a simple display name generator that replaces camel case names with readable sentences. To begin with, we can extend the DisplayNameGenerator.Standard class:

    static class ReplaceCamelCase extends DisplayNameGenerator.Standard {
        @Override
        public String generateDisplayNameForClass(Class<?> testClass) {
            return replaceCamelCase(super.generateDisplayNameForClass(testClass));
        }

        @Override
        public String generateDisplayNameForNestedClass(Class<?> nestedClass) {
            return replaceCamelCase(super.generateDisplayNameForNestedClass(nestedClass));
        }

        @Override
        public String generateDisplayNameForMethod(Class<?> testClass, Method testMethod) {
            return this.replaceCamelCase(testMethod.getName()) + 
              DisplayNameGenerator.parameterTypesAsString(testMethod);
        }

        String replaceCamelCase(String camelCase) {
            StringBuilder result = new StringBuilder();
            result.append(camelCase.charAt(0));
            for (int i=1; i<camelCase.length(); i++) {
                if (Character.isUpperCase(camelCase.charAt(i))) {
                    result.append(' ');
                    result.append(Character.toLowerCase(camelCase.charAt(i)));
                } else {
                    result.append(camelCase.charAt(i));
                }
            }
            return result.toString();
        }
    }

In the above example, we can see the methods that generate different parts of the display name.

Let's write a test for our generator:

@DisplayNameGeneration(DisplayNameGeneratorUnitTest.ReplaceCamelCase.class)
class DisplayNameGeneratorUnitTest {

    @Test
    void camelCaseName() {
    }
}

Next, when running the test, we can see that the camel case names have been replaced with readable sentences:

└─ Display name generator unit test ✓
   └─ camel case name() ✓

3.2. Indicative Sentences

So far, we've discussed very simple use cases. However, we can get more creative:

    static class IndicativeSentences extends ReplaceCamelCase {
        @Override
        public String generateDisplayNameForNestedClass(Class<?> nestedClass) {
            return super.generateDisplayNameForNestedClass(nestedClass) + "...";
        }

        @Override
        public String generateDisplayNameForMethod(Class<?> testClass, Method testMethod) {
            return replaceCamelCase(testClass.getSimpleName() + " " + testMethod.getName()) + ".";
        }
    }

The idea here is to create indicative sentences from the nested class and test method. In other words, the nested class name will be prepended to the test method name:

class DisplayNameGeneratorUnitTest {

    @Nested
    @DisplayNameGeneration(DisplayNameGeneratorUnitTest.IndicativeSentences.class)
    class ANumberIsFizz {
        @Test
        void ifItIsDivisibleByThree() {
        }

        @ParameterizedTest(name = "Number {0} is fizz.")
        @ValueSource(ints = { 3, 12, 18 })
        void ifItIsOneOfTheFollowingNumbers(int number) {
        }
    }

    @Nested
    @DisplayNameGeneration(DisplayNameGeneratorUnitTest.IndicativeSentences.class)
    class ANumberIsBuzz {
        @Test
        void ifItIsDivisibleByFive() {
        }

        @ParameterizedTest(name = "Number {0} is buzz.")
        @ValueSource(ints = { 5, 10, 20 })
        void ifItIsOneOfTheFollowingNumbers(int number) {
        }
    }
}

Looking at the example, we use the nested class as a context for the test method. To better illustrate the results, let's run the test:

└─ Display name generator unit test ✓
   ├─ A number is buzz... ✓
   │  ├─ A number is buzz if it is one of the following numbers. ✓
   │  │  ├─ Number 5 is buzz. ✓
   │  │  ├─ Number 10 is buzz. ✓
   │  │  └─ Number 20 is buzz. ✓
   │  └─ A number is buzz if it is divisible by five. ✓
   └─ A number is fizz... ✓
      ├─ A number is fizz if it is one of the following numbers. ✓
      │  ├─ Number 3 is fizz. ✓
      │  ├─ Number 12 is fizz. ✓
      │  └─ Number 18 is fizz. ✓
      └─ A number is fizz if it is divisible by three. ✓

As we can see, the generator combined the nested class and test method names to create indicative sentences.

4. Conclusion

In this tutorial, we saw how to use the @DisplayNameGeneration annotation to generate display names for our tests. Furthermore, we wrote our own DisplayNameGenerator to customize the display name generation.

As usual, the examples used in this article can be found in the GitHub project.

Generic bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
Comments are closed on this article!