JUnit 5 Annotations

Hi all, in this article, we will learn the basic JUnit 5 annotations. If you are coming from JUnit 4, you will see that some of the annotations changed in JUnit 5. All core annotations are located in the org.junit.jupiter.api package in the junit-jupiter-api module. Here are the annotations and their descriptions.

Annotation Description

@Test

This annotation denotes that a method is a test method. Unlike JUnit 4’s @Test annotation, this annotation does not declare any attributes, since test extensions in JUnit Jupiter operate based on their own dedicated annotations. Such methods are inherited unless they are overridden.

@ParameterizedTest

Denotes that a method is a parameterized test. Such methods are inherited unless they are overridden.

@RepeatedTest

Denotes that a method is a test template for a repeated test. Such methods are inherited unless they are overridden.

@TestFactory

Denotes that a method is a test factory for dynamic tests. Such methods are inherited unless they are overridden.

@TestInstance

Used to configure the test instance lifecycle for the annotated test class. Such annotations are inherited.

@TestTemplate

Denotes that a method is a template for test cases designed to be invoked multiple times depending on the number of invocation contexts returned by the registered providers. Such methods are inherited unless they are overridden.

@DisplayName

Declares a custom display name for the test class or test method. Such annotations are not inherited.

@BeforeEach

Denotes that the annotated method should be executed before each @Test@RepeatedTest@ParameterizedTest, or @TestFactory method in the current class; analogous to JUnit 4’s @Before. Such methods are inherited unless they are overridden.

@AfterEach

Denotes that the annotated method should be executed after each @Test@RepeatedTest@ParameterizedTest, or @TestFactory method in the current class; analogous to JUnit 4’s @After. Such methods are inherited unless they are overridden.

@BeforeAll

Denotes that the annotated method should be executed before all @Test@RepeatedTest@ParameterizedTest, and @TestFactory methods in the current class; analogous to JUnit 4’s @BeforeClass. Such methods are inherited (unless they are hidden or overridden) and must be static (unless the “per-class” test instance lifecycle is used).

@AfterAll

Denotes that the annotated method should be executed after all @Test@RepeatedTest@ParameterizedTest, and @TestFactory methods in the current class; analogous to JUnit 4’s @AfterClass. Such methods are inherited (unless they are hidden or overridden) and must be static (unless the “per-class” test instance lifecycle is used).

@Nested

Denotes that the annotated class is a nested, non-static test class. @BeforeAll and @AfterAllmethods cannot be used directly in a @Nested test class unless the “per-class” test instance lifecycleis used. Such annotations are not inherited.

@Tag

Used to declare tags for filtering tests, either at the class or method level; analogous to test groups in TestNG or Categories in JUnit 4. Such annotations are inherited at the class level but not at the method level.

@Disabled

Used to disable a test class or test method; analogous to JUnit 4’s @Ignore. Such annotations are not inherited.

@ExtendWith

Used to register custom extensions. Such annotations are inherited.

 

JUnit 5 Annotations Examples

Let’s write some example test codes for the JUnit 5 annotations.

First, we need to add JUnit 5 dependency in our pom.xml.

<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-engine</artifactId>
    <version>5.3.1</version>
    <scope>test</scope>
</dependency>

@Test Annotation

class JUnit5TestAnnotationTest{
    @Test
    void myTestAnnotationTest() {
        assertEquals(4, 2 + 2);
    }
}

@ParameterizedTest

@ParameterizedTest
@ValueSource(strings = { "Jack", "Jane", "Michael" })
void genderByNameTest(String name) {
    assertTrue(isMan(name));
}

@RepeatTest

@RepeatedTest(10)
void repeatedTest() {
   // Repeats 10 times.
}

@RepeatedTest(3)
void repeatedTestWithRepetitionInfo(RepetitionInfo repetitionInfo) {
    assertEquals(3, repetitionInfo.getTotalRepetitions());
}

@RepeatedTest(value = 1, name = "{displayName} {currentRepetition}/{totalRepetitions}")
@DisplayName("My Repeat Test!")
void customRepeatTestDisplayName(TestInfo testInfo) {
    assertEquals(testInfo.getDisplayName(), "Repeat! 1/1");
}

@DisplayName

@DisplayName("My Test Class Name")
class DisplayNameTest {
    @Test
    @DisplayName("My Test Name")
    void testWithDisplayName() {
    }
}

@BeforeEach & @AfterEach

class MyTestClass{
    @BeforeEach
    void setup() {
        log.info("@BeforeEach executes before each test method in this class.");
    }

    @Test
    void myTestMethod() {
    }

    @AfterEach
    void tearDown() {
        log.info("@AfterEach executes after each test method in this class.");
    }
}

@BeforeAll & @AfterAll

class MyTestClass{
    @BeforeAll
    static void initAll() {
          log.info("@BeforeAll executes once before all test methods in this class.");
    }

    @Test
    void myTestMethod() {
    }

    @AfterAll
    static void tearDownAll() {
          log.info("@AfterAll executes once after all test methods in this class.");
    }
}

@Nested

Nested annotation denotes that the annotated class is nested, it is a non-static test class. @BeforeAll and @AfterAll methods cannot be used directly in a @Nested test class. It gives us more capabilities to declare the connection between several test groups.

@DisplayName("A stack")
class TestingAStackDemo {

    Stack<Object> stack;

    @Test
    @DisplayName("is instantiated with new Stack()")
    void isInstantiatedWithNew() {
        new Stack<>();
    }

    @Nested
    @DisplayName("when new")
    class WhenNew {

        @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());
            }
        }
    }

Ref: https://junit.org/junit5/docs/current/user-guide/#writing-tests-nested
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 considered to be full members of the test class family with one exception: @BeforeAll and @AfterAll methods do not work by default. The reason is that Java does not allow static members in inner classes. However, this restriction can be circumvented by annotating a @Nested test class with @TestInstance(Lifecycle.PER_CLASS) (see Test Instance Lifecycle).

@Tag

With this annotation, we can give a tag to tests for filtering them. We can do this either at the class or method level.

@Tag("Smoke")
@Tag("Critical")
class MySampleTagTestClass {
    @Test
    @Tag("basket")
    void addToBasketTest() {
    }
}

@Disabled

This annotation is used to disable a test class or test method.

//Dısable a Test Class
@Disabled
class DisabledClassDemo {
    @Test
    void testWillBeSkipped() {
    }
}

//Disable a test method
class DisabledTestsDemo {
    @Disabled
    @Test
    void testWillBeSkipped() {
    }
    @Test
    void testWillBeExecuted() {
    }
}

@ExtendWith

This annotation is used to extend the capabilities of JUnit 5. You can create listeners and so on with extensions. It is like a new Rules of JUnit. Usage of a custom extension is shown below.

@ExtendWith(MyExtension.class)
@Test
void test() {
    // ...
}

@TestFactory

We can use this annotation for Dynamic Tests. Dynamic tests are generated at runtime by a factory method and it is annotated with @TestFactory. Below example is gathered from the original JUnit website for more details please refer here.

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.DynamicContainer.dynamicContainer;
import static org.junit.jupiter.api.DynamicTest.dynamicTest;

import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.function.Function;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import org.junit.jupiter.api.DynamicNode;
import org.junit.jupiter.api.DynamicTest;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.TestFactory;
import org.junit.jupiter.api.function.ThrowingConsumer;

class DynamicTestsDemo {

    // This will result in a JUnitException!
    @TestFactory
    List<String> dynamicTestsWithInvalidReturnType() {
        return Arrays.asList("Hello");
    }

    @TestFactory
    Collection<DynamicTest> dynamicTestsFromCollection() {
        return Arrays.asList(
            dynamicTest("1st dynamic test", () -> assertTrue(true)),
            dynamicTest("2nd dynamic test", () -> assertEquals(4, 2 * 2))
        );
    }

    @TestFactory
    Iterable<DynamicTest> dynamicTestsFromIterable() {
        return Arrays.asList(
            dynamicTest("3rd dynamic test", () -> assertTrue(true)),
            dynamicTest("4th dynamic test", () -> assertEquals(4, 2 * 2))
        );
    }

    @TestFactory
    Iterator<DynamicTest> dynamicTestsFromIterator() {
        return Arrays.asList(
            dynamicTest("5th dynamic test", () -> assertTrue(true)),
            dynamicTest("6th dynamic test", () -> assertEquals(4, 2 * 2))
        ).iterator();
    }

    @TestFactory
    DynamicTest[] dynamicTestsFromArray() {
        return new DynamicTest[] {
            dynamicTest("7th dynamic test", () -> assertTrue(true)),
            dynamicTest("8th dynamic test", () -> assertEquals(4, 2 * 2))
        };
    }

    @TestFactory
    Stream<DynamicTest> dynamicTestsFromStream() {
        return Stream.of("A", "B", "C")
            .map(str -> dynamicTest("test" + str, () -> { /* ... */ }));
    }

    @TestFactory
    Stream<DynamicTest> dynamicTestsFromIntStream() {
        // Generates tests for the first 10 even integers.
        return IntStream.iterate(0, n -> n + 2).limit(10)
            .mapToObj(n -> dynamicTest("test" + n, () -> assertTrue(n % 2 == 0)));
    }

    @TestFactory
    Stream<DynamicTest> generateRandomNumberOfTests() {

        // Generates random positive integers between 0 and 100 until
        // a number evenly divisible by 7 is encountered.
        Iterator<Integer> inputGenerator = new Iterator<>() {

            Random random = new Random();
            int current;

            @Override
            public boolean hasNext() {
                current = random.nextInt(100);
                return current % 7 != 0;
            }

            @Override
            public Integer next() {
                return current;
            }
        };

        // Generates display names like: input:5, input:37, input:85, etc.
        Function<Integer, String> displayNameGenerator = (input) -> "input:" + input;

        // Executes tests based on the current input value.
        ThrowingConsumer<Integer> testExecutor = (input) -> assertTrue(input % 7 != 0);

        // Returns a stream of dynamic tests.
        return DynamicTest.stream(inputGenerator, displayNameGenerator, testExecutor);
    }

    @TestFactory
    Stream<DynamicNode> dynamicTestsWithContainers() {
        return Stream.of("A", "B", "C")
            .map(input -> dynamicContainer("Container " + input, Stream.of(
                dynamicTest("not null", () -> assertNotNull(input)),
                dynamicContainer("properties", Stream.of(
                    dynamicTest("length > 0", () -> assertTrue(input.length() > 0)),
                    dynamicTest("not empty", () -> assertFalse(input.isEmpty()))
                ))
            )));
    }
}

For @TestInstance and @TestTemplate you can get information at official JUnit 5 documentation page.

Thanks.
Onur Baskirt

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.