JUnit5 Annotations

JUnit5 is the latest version of JUnit. Many new JUnit5 annotations are introduced like @Disabled, @Tag and many annotations of JUnit4 no longer exist like @After, @Before, @Category, and more. In this blog, we will highlight some of the new JUnit5 annotations. If you are new to JUnit 5, you may refer to our blog “What is JUnit5.”

1. @Test

This annotation denotes a method to be a test method. Also, there is no need for any attribute, unlike JUnit 4 @Test annotation.

import static org.junit.jupiter.api.Assertions.assertEquals;

import org.junit.jupiter.api.Test;

class FirstJUnit5Tests {

	@Test
	void myFirstTest() {
		assertEquals(2, 1 + 1);
	}

}

2. @ParameterizedTest

This annotation denotes an annotated method as a parameterized test method. It allows running a test multiple times with different arguments.

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import static org.junit.jupiter.api.Assertions.assertTrue;

class JUnit5Test {

	@ParameterizedTest
	@ValueSource(strings = { "call", "call2", "call3" })
	void endsWithI(String str) {
		assertTrue(str.endsWith("i"));
	}
}

3. @RepeatedTest

This annotation denotes an annotated method as a test template method. It allows a test to repeat a specified number of times. Just by declaring a method with @RepeatedTest annotation and specifying the number of times the method to repeat. Also, every invocation of @RepeatedTest behaves like execution of @Test annotation. We cannot annotate private or static methods as a @RepeatedTest method and must return void.

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.RepetitionInfo;
import org.junit.jupiter.api.TestInfo;

import static org.junit.jupiter.api.Assertions.assertEquals;

class JUnit5Test {
    
    @RepeatedTest(value = 5, name = "{displayName} {currentRepetition}/{totalRepetitions}")
    @DisplayName("RepeatingTest")
    void customDisplayName(RepetitionInfo repInfo, TestInfo testInfo) {
        int i = 3;
        System.out.println(testInfo.getDisplayName() + 
            "-->" + repInfo.getCurrentRepetition()
        );
        
        assertEquals(repInfo.getCurrentRepetition(), i);
    }
}import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.RepetitionInfo;
import org.junit.jupiter.api.TestInfo;

import static org.junit.jupiter.api.Assertions.assertEquals;

class JUnit5Test {
    
    @RepeatedTest(value = 5, name = "{displayName} {currentRepetition}/{totalRepetitions}")
    @DisplayName("RepeatingTest")
    void customDisplayName(RepetitionInfo repInfo, TestInfo testInfo) {
        int i = 3;
        System.out.println(testInfo.getDisplayName() + 
            "-->" + repInfo.getCurrentRepetition()
        );
        
        assertEquals(repInfo.getCurrentRepetition(), i);
    }
}

4. @TestFactory

This annotation denotes an annotated method as a test factory method. Unlike @Test, it is not a test case as it is a factory of test cases. We cannot annotate private or static methods as a @TestFactory method and must return collection, iterable, stream, and an array of a DynamicNode.

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.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
	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<Integer>() {

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

}

5. @DisplayName

This annotation declares custom names for test cases and methods used for test reports in IDEs. They can contain special characters, space, and emojis.

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;

@DisplayName("DisplayName 1")
class JUnit5Test {
	@Test
	@DisplayName("test name 1")
	void testWithDisplayName() {
	}

	@Test
	@DisplayName("test name 2")
	void printDisplayName(TestInfo testInfo) {
		System.out.println(testInfo.getDisplayName());
	}
}

6. @BeforeEach

This annotation denotes an annotated method should execute before each @Test method for a given test case. We cannot annotate private or static methods as a @BeforeEach method and must have a void return type.

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class StandardTests {
	@BeforeEach
	void init() {
	}

	@Test
	void succeedingTest() {
	}

	@AfterEach
	void tearDown() {
	}
}

7. @AfterEach

In contrast to @BeforeEach, this annotation denotes an annotated method should execute after each @Test annotated method for a given test case. @AfterEach annotated method must not be private or static and must have a void return type.

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class StandardTests {
	@BeforeEach
	void init() {
	}

	@Test
	void succeedingTest() {
	}

	@AfterEach
	void tearDown() {
	}
}

8. @BeforeAll

This annotation denotes a method should execute before all @Test methods for a given test case. @BeforeAll methods must not be private or static and must have a void return type.

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

class StandardTests {

	@BeforeAll
	static void initAll() {
	}

	@Test
	void succeedingTest() {
	}

	@AfterAll
	static void tearDownAll() {
	}
}

9. @AfterAll

This annotation denotes that an annotated method should execute after all @Test annotated methods for a given test case. @AfterAll annotated methods must not be private or static and must have a void return type.

All the annotated @BeforeAll methods and @AfterAll methods are only executed once for a given test case.

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

class StandardTests {

	@BeforeAll
	static void initAll() {
	}

	@Test
	void succeedingTest() {
	}

	@AfterAll
	static void tearDownAll() {
	}
}

10. @Nested

This annotation denotes that an inner class to be a test class, i.e., an annotated class is a non-static nested test class. In nested class, @BeforeAll and @AfterAll annotated methods cannot be directly used unless the “pre-class” is used.

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.EmptyStackException;
import java.util.Stack;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

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

11. @Tag

This annotation is a repeatable annotation declare a tag for the annotated test class. @Tag is used to filter the test. In addition, they help to create test packs with selected tests.

import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

@Tag("smoke")
class JUnit5Test {

	@Test
	@Tag("login")
	void validLoginTest() {
	}

	@Test
	@Tag("search")
	void searchTest() {
	}
}

12. @Disable

This annotation denotes that an annotated test class or a test method is disabled or ignored and should not executed. When declared at the class level, all @Test annotated methods are disabled automatically, while at the method level, only the annotated method is disabled.

import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

@Disabled
class DisabledClassDemo {
	@Test
	void testWillBeSkipped() {
	}
}

13. @ExtendWith

This annotation is a repeatable annotation used to list custom extensions for the annotated test class or test method.

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

Conclusion

Moreover, with the benefits of new and improved JUnit5 annotations, JUnit 5 is more integrated with Java 8 features, especially lambda expressions. In the next blog, we will introduce assertions and assumptions in JUnit5.

Some links on this page are affiliate links. This means that if you choose to make a purchase, we may earn a small commission at no extra cost to you. For more information, Go here

Leave a Reply

Your email address will not be published. Required fields are marked *