Overview

The goal of this document is to provide comprehensive reference documentation for programmers writing tests, extension authors, and engine authors as well as build tool and IDE vendors.

What is JUnit 5?

Unlike previous versions of JUnit, JUnit 5 is composed of several different modules from three different sub-projects.

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

The JUnit Platform serves as a foundation for launching testing frameworks on the JVM. It also defines the TestEngine API for developing a testing framework that runs on the platform. Furthermore, the platform provides a Console Launcher to launch the platform from the command line and the JUnit Platform Suite Engine for running a custom test suite using one or more test engines on the platform. First-class support for the JUnit Platform also exists in popular IDEs (see IntelliJ IDEA, Eclipse, NetBeans, and Visual Studio Code) and build tools (see Gradle, Maven, and Ant).

JUnit Jupiter is the combination of the programming model and extension model for writing tests and extensions in JUnit 5. The Jupiter sub-project provides a TestEngine for running Jupiter based tests on the platform.

JUnit Vintage provides a TestEngine for running JUnit 3 and JUnit 4 based tests on the platform. It requires JUnit 4.12 or later to be present on the class path or module path.

Supported Java Versions

JUnit 5 requires Java 8 (or higher) at runtime. However, you can still test code that has been compiled with previous versions of the JDK.

Getting Help

Ask JUnit 5 related questions on Stack Overflow or use the Q&A category on GitHub Discussions.

Getting Started

Downloading JUnit Artifacts

To find out what artifacts are available for download and inclusion in your project, refer to Dependency Metadata. To set up dependency management for your build, refer to Build Support and the Example Projects.

JUnit 5 Features

To find out what features are available in JUnit 5 and how to use them, read the corresponding sections of this User Guide, organized by topic.

Example Projects

To see complete, working examples of projects that you can copy and experiment with, the junit-examples repository is a good place to start. The junit-examples repository hosts a collection of example projects based on JUnit Jupiter, JUnit Vintage, and other testing frameworks. You’ll find appropriate build scripts (e.g., build.gradle, pom.xml, etc.) in the example projects. The links below highlight some of the combinations you can choose from.

Writing Tests

The following example provides a glimpse at the minimum requirements for writing a test in JUnit Jupiter. Subsequent sections of this chapter will provide further details on all available features.

A first test case
import static org.junit.jupiter.api.Assertions.assertEquals;

import example.util.Calculator;

import org.junit.jupiter.api.Test;

class MyFirstJUnitJupiterTests {

	private final Calculator calculator = new Calculator();

	@Test
	void addition() {
		assertEquals(2, calculator.add(1, 1));
	}

}

Annotations

JUnit Jupiter supports the following annotations for configuring tests and extending the framework.

Unless otherwise stated, all core annotations are located in the org.junit.jupiter.api package in the junit-jupiter-api module.

@Test

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.

@TestTemplate

Denotes that a method is a template for a test case 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.

@TestClassOrder

Used to configure the test class execution order for @Nested test classes in the annotated test class. Such annotations are inherited.

@TestMethodOrder

Used to configure the test method execution order for the annotated test class; similar to JUnit 4’s @FixMethodOrder. Such annotations are inherited.

@TestInstance

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

@DisplayName

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

@DisplayNameGeneration

Declares a custom display name generator for the test class. Such annotations are 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 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 overridden and must be static unless the "per-class" test instance lifecycle is used.

@ParameterizedClass

Denotes that the annotated class is a parameterized class. Such annotations are inherited.

@BeforeParameterizedClassInvocation

Denotes that the annotated method should be executed once before each invocation of a parameterized class. Such methods are inherited unless they are overridden.

@AfterParameterizedClassInvocation

Denotes that the annotated method should be executed once after each invocation of a parameterized class. Such methods are inherited unless they are overridden.

@ClassTemplate

Denotes that the annotated class is a template for a test class designed to be executed multiple times depending on the number of invocation contexts returned by the registered providers. Such annotations are inherited.

@Nested

Denotes that the annotated class is a non-static nested test class. On Java 8 through Java 15, @BeforeAll and @AfterAll methods cannot be used directly in a @Nested test class unless the "per-class" test instance lifecycle is used. Beginning with Java 16, @BeforeAll and @AfterAll methods can be declared as static in a @Nested test class with either test instance lifecycle mode. 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.

@AutoClose

Denotes that the annotated field represents a resource that will be automatically closed after test execution. Such fields are inherited.

@Timeout

Used to fail a test, test factory, test template, or lifecycle method if its execution exceeds a given duration. Such annotations are inherited.

@TempDir

Used to supply a temporary directory via field injection or parameter injection in a test class constructor, lifecycle method, or test method; located in the org.junit.jupiter.api.io package. Such fields are inherited.

@ExtendWith

Used to register extensions declaratively. Such annotations are inherited.

@RegisterExtension

Used to register extensions programmatically via fields. Such fields are inherited.

Some annotations may currently be experimental. Consult the table in Experimental APIs for details.

Meta-Annotations and Composed Annotations

JUnit Jupiter annotations can be used as meta-annotations. That means that you can define your own composed annotation that will automatically inherit the semantics of its meta-annotations.

For example, instead of copying and pasting @Tag("fast") throughout your code base (see Tagging and Filtering), you can create a custom composed annotation named @Fast as follows. @Fast can then be used as a drop-in replacement for @Tag("fast").

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.junit.jupiter.api.Tag;

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Tag("fast")
public @interface Fast {
}

The following @Test method demonstrates usage of the @Fast annotation.

@Fast
@Test
void myFastTest() {
    // ...
}

You can even take that one step further by introducing a custom @FastTest annotation that can be used as a drop-in replacement for @Tag("fast") and @Test.

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Tag("fast")
@Test
public @interface FastTest {
}

JUnit automatically recognizes the following as a @Test method that is tagged with "fast".

@FastTest
void myFastTest() {
    // ...
}

Definitions

Platform Concepts

Container

a node in the test tree that contains other containers or tests as its children (e.g. a test class).

Test

a node in the test tree that verifies expected behavior when executed (e.g. a @Test method).

Jupiter Concepts

Lifecycle Method

any method that is directly annotated or meta-annotated with @BeforeAll, @AfterAll, @BeforeEach, or @AfterEach.

Test Class

any top-level class, static member class, or @Nested class that contains at least one test method, i.e. a container. Test classes must not be abstract and must have a single constructor. Java record classes are supported as well.

Test Method

any instance method that is directly annotated or meta-annotated with @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, or @TestTemplate. With the exception of @Test, these create a container in the test tree that groups tests or, potentially (for @TestFactory), other containers.

Test Classes and Methods

Test methods and lifecycle methods may be declared locally within the current test class, inherited from superclasses, or inherited from interfaces (see Test Interfaces and Default Methods). In addition, test methods and lifecycle methods must not be abstract and must not return a value (except @TestFactory methods which are required to return a value).

Class and method visibility

Test classes, test methods, and lifecycle methods are not required to be public, but they must not be private.

It is generally recommended to omit the public modifier for test classes, test methods, and lifecycle methods unless there is a technical reason for doing so – for example, when a test class is extended by a test class in another package. Another technical reason for making classes and methods public is to simplify testing on the module path when using the Java Module System.

Field and method inheritance

Fields in test classes are inherited. For example, a @TempDir field from a superclass will always be applied in a subclass.

Test methods and lifecycle methods are inherited unless they are overridden according to the visibility rules of the Java language. For example, a @Test method from a superclass will always be applied in a subclass unless the subclass explicitly overrides the method. Similarly, if a package-private @Test method is declared in a superclass that resides in a different package than the subclass, that @Test method will always be applied in the subclass since the subclass cannot override a package-private method from a superclass in a different package.

The following test class demonstrates the use of @Test methods and all supported lifecycle methods. For further information on runtime semantics, see Test Execution Order and Wrapping Behavior of Callbacks.

A standard test class
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

class StandardTests {

	@BeforeAll
	static void initAll() {
	}

	@BeforeEach
	void init() {
	}

	@Test
	void succeedingTest() {
	}

	@Test
	void failingTest() {
		fail("a failing test");
	}

	@Test
	@Disabled("for demonstration purposes")
	void skippedTest() {
		// not executed
	}

	@Test
	void abortedTest() {
		assumeTrue("abc".contains("Z"));
		fail("test should have been aborted");
	}

	@AfterEach
	void tearDown() {
	}

	@AfterAll
	static void tearDownAll() {
	}

}

It is also possible to use Java record classes as test classes as illustrated by the following example.

A test class written as a Java record
import static org.junit.jupiter.api.Assertions.assertEquals;

import example.util.Calculator;

import org.junit.jupiter.api.Test;

record MyFirstJUnitJupiterRecordTests() {

	@Test
	void addition() {
		assertEquals(2, new Calculator().add(1, 1));
	}

}

Display Names

Test classes and test methods can declare custom display names via @DisplayName — with spaces, special characters, and even emojis — that will be displayed in test reports and by test runners and IDEs.

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

@DisplayName("A special test case")
class DisplayNameDemo {

	@Test
	@DisplayName("Custom test name containing spaces")
	void testWithDisplayNameContainingSpaces() {
	}

	@Test
	@DisplayName("╯°□°)╯")
	void testWithDisplayNameContainingSpecialCharacters() {
	}

	@Test
	@DisplayName("😱")
	void testWithDisplayNameContainingEmoji() {
	}

}

Display Name Generators

JUnit Jupiter supports custom display name generators that can be configured via the @DisplayNameGeneration annotation.

Generators can be created by implementing the DisplayNameGenerator API. The following table lists the default display name generators available in Jupiter.

DisplayNameGenerator Behavior

Standard

Matches the standard display name generation behavior in place since JUnit Jupiter 5.0 was released.

Simple

Extends the functionality of Standard by removing trailing parentheses for methods with no parameters.

ReplaceUnderscores

Replaces underscores with spaces.

IndicativeSentences

Generates complete sentences by concatenating the names of the test and the enclosing classes.

Values provided via @DisplayName annotations always take precedence over display names generated by a DisplayNameGenerator.

The following example demonstrates the use of the ReplaceUnderscores display name generator.

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

	@Test
	void if_it_is_zero() {
	}

	@DisplayName("A negative value for year is not supported by the leap year computation.")
	@ParameterizedTest(name = "For example, year {0} is not supported.")
	@ValueSource(ints = { -1, -4 })
	void if_it_is_negative(int year) {
	}

}

Running the above test class results in the following display names.

A year is not supported ✔
├─ if it is zero ✔
└─ A negative value for year is not supported by the leap year computation. ✔
   ├─ For example, year -1 is not supported. ✔
   └─ For example, year -4 is not supported. ✔

With the IndicativeSentences display name generator, you can customize the separator and the underlying generator by using @IndicativeSentencesGeneration as shown in the following example.

@IndicativeSentencesGeneration(separator = " -> ", generator = ReplaceUnderscores.class)
class A_year_is_a_leap_year {

	@Test
	void if_it_is_divisible_by_4_but_not_by_100() {
	}

	@ParameterizedTest(name = "Year {0} is a leap year.")
	@ValueSource(ints = { 2016, 2020, 2048 })
	void if_it_is_one_of_the_following_years(int year) {
	}

}

Running the above test class results in the following display names.

A year is a leap year ✔
├─ A year is a leap year -> if it is divisible by 4 but not by 100 ✔
└─ A year is a leap year -> if it is one of the following years ✔
   ├─ Year 2016 is a leap year. ✔
   ├─ Year 2020 is a leap year. ✔
   └─ Year 2048 is a leap year. ✔

With IndicativeSentences, you can optionally specify custom sentence fragments via the @SentenceFragment annotation as demonstrated in the following example.

@SentenceFragment("A year is a leap year")
@IndicativeSentencesGeneration
class LeapYearTests {

	@SentenceFragment("if it is divisible by 4 but not by 100")
	@Test
	void divisibleBy4ButNotBy100() {
	}

	@SentenceFragment("if it is one of the following years")
	@ParameterizedTest(name = "{0}")
	@ValueSource(ints = { 2016, 2020, 2048 })
	void validLeapYear(int year) {
	}

}

Running the above test class results in the following display names.

A year is a leap year ✔
├─ A year is a leap year, if it is divisible by 4 but not by 100 ✔
└─ A year is a leap year, if it is one of the following years ✔
   ├─ 2016 ✔
   ├─ 2020 ✔
   └─ 2048 ✔

Setting the Default Display Name Generator

You can use the junit.jupiter.displayname.generator.default configuration parameter to specify the fully qualified class name of the DisplayNameGenerator you would like to use by default. Just like for display name generators configured via the @DisplayNameGeneration annotation, the supplied class has to implement the DisplayNameGenerator interface. The default display name generator will be used for all tests unless the @DisplayNameGeneration annotation is present on an enclosing test class or test interface. Values provided via @DisplayName annotations always take precedence over display names generated by a DisplayNameGenerator.

For example, to use the ReplaceUnderscores display name generator by default, you should set the configuration parameter to the corresponding fully qualified class name (e.g., in src/test/resources/junit-platform.properties):

junit.jupiter.displayname.generator.default = \
    org.junit.jupiter.api.DisplayNameGenerator$ReplaceUnderscores

Similarly, you can specify the fully qualified name of any custom class that implements DisplayNameGenerator.

In summary, the display name for a test class or method is determined according to the following precedence rules:

  1. value of the @DisplayName annotation, if present

  2. by calling the DisplayNameGenerator specified in the @DisplayNameGeneration annotation, if present

  3. by calling the default DisplayNameGenerator configured via the configuration parameter, if present

  4. by calling org.junit.jupiter.api.DisplayNameGenerator.Standard

Assertions

JUnit Jupiter comes with many of the assertion methods that JUnit 4 has and adds a few that lend themselves well to being used with Java 8 lambdas. All JUnit Jupiter assertions are static methods in the org.junit.jupiter.api.Assertions class.

Assertion methods optionally accept the assertion message as their third parameter, which can be either a String or a Supplier<String>.

When using a Supplier<String> (e.g., a lambda expression), the message is evaluated lazily. This can provide a performance benefit, especially if message construction is complex or time-consuming, as it is only evaluated when the assertion fails.

import static java.time.Duration.ofMillis;
import static java.time.Duration.ofMinutes;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTimeout;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.util.concurrent.CountDownLatch;

import example.domain.Person;
import example.util.Calculator;

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

class AssertionsDemo {

	private final Calculator calculator = new Calculator();

	private final Person person = new Person("Jane", "Doe");

	@Test
	void standardAssertions() {
		assertEquals(2, calculator.add(1, 1));
		assertEquals(4, calculator.multiply(2, 2),
				"The optional failure message is now the last parameter");

		// Lazily evaluates generateFailureMessage('a','b').
		assertTrue('a' < 'b', () -> generateFailureMessage('a','b'));
	}

	@Test
	void groupedAssertions() {
		// In a grouped assertion all assertions are executed, and all
		// failures will be reported together.
		assertAll("person",
			() -> assertEquals("Jane", person.getFirstName()),
			() -> assertEquals("Doe", person.getLastName())
		);
	}

	@Test
	void dependentAssertions() {
		// Within a code block, if an assertion fails the
		// subsequent code in the same block will be skipped.
		assertAll("properties",
			() -> {
				String firstName = person.getFirstName();
				assertNotNull(firstName);

				// Executed only if the previous assertion is valid.
				assertAll("first name",
					() -> assertTrue(firstName.startsWith("J")),
					() -> assertTrue(firstName.endsWith("e"))
				);
			},
			() -> {
				// Grouped assertion, so processed independently
				// of results of first name assertions.
				String lastName = person.getLastName();
				assertNotNull(lastName);

				// Executed only if the previous assertion is valid.
				assertAll("last name",
					() -> assertTrue(lastName.startsWith("D")),
					() -> assertTrue(lastName.endsWith("e"))
				);
			}
		);
	}

	@Test
	void exceptionTesting() {
		Exception exception = assertThrows(ArithmeticException.class, () ->
			calculator.divide(1, 0));
		assertEquals("/ by zero", exception.getMessage());
	}

	@Test
	void timeoutNotExceeded() {
		// The following assertion succeeds.
		assertTimeout(ofMinutes(2), () -> {
			// Perform task that takes less than 2 minutes.
		});
	}

	@Test
	void timeoutNotExceededWithResult() {
		// The following assertion succeeds, and returns the supplied object.
		String actualResult = assertTimeout(ofMinutes(2), () -> {
			return "a result";
		});
		assertEquals("a result", actualResult);
	}

	@Test
	void timeoutNotExceededWithMethod() {
		// The following assertion invokes a method reference and returns an object.
		String actualGreeting = assertTimeout(ofMinutes(2), AssertionsDemo::greeting);
		assertEquals("Hello, World!", actualGreeting);
	}

	@Test
	void timeoutExceeded() {
		// The following assertion fails with an error message similar to:
		// execution exceeded timeout of 10 ms by 91 ms
		assertTimeout(ofMillis(10), () -> {
			// Simulate task that takes more than 10 ms.
			Thread.sleep(100);
		});
	}

	@Test
	void timeoutExceededWithPreemptiveTermination() {
		// The following assertion fails with an error message similar to:
		// execution timed out after 10 ms
		assertTimeoutPreemptively(ofMillis(10), () -> {
			// Simulate task that takes more than 10 ms.
			new CountDownLatch(1).await();
		});
	}

	private static String greeting() {
		return "Hello, World!";
	}

	private static String generateFailureMessage(char a, char b) {
		return "Assertion messages can be lazily evaluated -- "
				+ "to avoid constructing complex messages unnecessarily." + (a < b);
	}
}
Preemptive Timeouts with assertTimeoutPreemptively()

The various assertTimeoutPreemptively() methods in the Assertions class execute the provided executable or supplier in a different thread than that of the calling code. This behavior can lead to undesirable side effects if the code that is executed within the executable or supplier relies on java.lang.ThreadLocal storage.

One common example of this is the transactional testing support in the Spring Framework. Specifically, Spring’s testing support binds transaction state to the current thread (via a ThreadLocal) before a test method is invoked. Consequently, if an executable or supplier provided to assertTimeoutPreemptively() invokes Spring-managed components that participate in transactions, any actions taken by those components will not be rolled back with the test-managed transaction. On the contrary, such actions will be committed to the persistent store (e.g., relational database) even though the test-managed transaction is rolled back.

Similar side effects may be encountered with other frameworks that rely on ThreadLocal storage.

Kotlin Assertion Support

JUnit Jupiter also comes with a few assertion methods that lend themselves well to being used in Kotlin. All JUnit Jupiter Kotlin assertions are top-level functions in the org.junit.jupiter.api package.

import example.domain.Person
import example.util.Calculator
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Tag
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertAll
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.assertInstanceOf
import org.junit.jupiter.api.assertNotNull
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.assertTimeout
import org.junit.jupiter.api.assertTimeoutPreemptively
import java.time.Duration

class KotlinAssertionsDemo {
    private val person = Person("Jane", "Doe")
    private val people = setOf(person, Person("John", "Doe"))

    @Test
    fun `exception absence testing`() {
        val calculator = Calculator()
        val result =
            assertDoesNotThrow("Should not throw an exception") {
                calculator.divide(0, 1)
            }
        assertEquals(0, result)
    }

    @Test
    fun `expected exception testing`() {
        val calculator = Calculator()
        val exception =
            assertThrows<ArithmeticException> ("Should throw an exception") {
                calculator.divide(1, 0)
            }
        assertEquals("/ by zero", exception.message)
    }

    @Test
    fun `grouped assertions`() {
        assertAll(
            "Person properties",
            { assertEquals("Jane", person.firstName) },
            { assertEquals("Doe", person.lastName) }
        )
    }

    @Test
    fun `grouped assertions from a stream`() {
        assertAll(
            "People with first name starting with J",
            people
                .stream()
                .map {
                    // This mapping returns Stream<() -> Unit>
                    { assertTrue(it.firstName.startsWith("J")) }
                }
        )
    }

    @Test
    fun `grouped assertions from a collection`() {
        assertAll(
            "People with last name of Doe",
            people.map { { assertEquals("Doe", it.lastName) } }
        )
    }

    @Test
    fun `timeout not exceeded testing`() {
        val fibonacciCalculator = FibonacciCalculator()
        val result =
            assertTimeout(Duration.ofMillis(1000)) {
                fibonacciCalculator.fib(14)
            }
        assertEquals(377, result)
    }

    @Test
    fun `timeout exceeded with preemptive termination`() {
        // The following assertion fails with an error message similar to:
        // execution timed out after 10 ms
        assertTimeoutPreemptively(Duration.ofMillis(10)) {
            // Simulate task that takes more than 10 ms.
            Thread.sleep(100)
        }
    }

    @Test
    fun `assertNotNull with a smart cast`() {
        val nullablePerson: Person? = person

        assertNotNull(nullablePerson)

        // The compiler smart casts nullablePerson to a non-nullable object.
        // The safe call operator (?.) isn't required.
        assertEquals(person.firstName, nullablePerson.firstName)
        assertEquals(person.lastName, nullablePerson.lastName)
    }

    @Test
    fun `assertInstanceOf with a smart cast`() {
        val maybePerson: Any = person

        assertInstanceOf<Person>(maybePerson)

        // The compiler smart casts maybePerson to a Person object,
        // allowing to access the Person properties.
        assertEquals(person.firstName, maybePerson.firstName)
        assertEquals(person.lastName, maybePerson.lastName)
    }
}

Third-party Assertion Libraries

Even though the assertion facilities provided by JUnit Jupiter are sufficient for many testing scenarios, there are times when more power and additional functionality such as matchers are desired or required. In such cases, the JUnit team recommends the use of third-party assertion libraries such as AssertJ, Hamcrest, Truth, etc. Developers are therefore free to use the assertion library of their choice.

For example, the combination of matchers and a fluent API can be used to make assertions more descriptive and readable. However, JUnit Jupiter’s org.junit.jupiter.api.Assertions class does not provide an assertThat() method like the one found in JUnit 4’s org.junit.Assert class which accepts a Hamcrest Matcher. Instead, developers are encouraged to use the built-in support for matchers provided by third-party assertion libraries.

The following example demonstrates how to use the assertThat() support from Hamcrest in a JUnit Jupiter test. As long as the Hamcrest library has been added to the classpath, you can statically import methods such as assertThat(), is(), and equalTo() and then use them in tests like in the assertWithHamcrestMatcher() method below.

import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;

import example.util.Calculator;

import org.junit.jupiter.api.Test;

class HamcrestAssertionsDemo {

	private final Calculator calculator = new Calculator();

	@Test
	void assertWithHamcrestMatcher() {
		assertThat(calculator.subtract(4, 1), is(equalTo(3)));
	}

}

Naturally, legacy tests based on the JUnit 4 programming model can continue using org.junit.Assert#assertThat.

Assumptions

Assumptions are typically used whenever it does not make sense to continue execution of a given test — for example, if the test depends on something that does not exist in the current runtime environment.

  • When an assumption is valid, the assumption method does not throw an exception, and execution of the test continues as usual.

  • When an assumption is invalid, the assumption method throws an exception of type org.opentest4j.TestAbortedException to signal that the test should be aborted instead of marked as a failure.

JUnit Jupiter comes with a subset of the assumption methods that JUnit 4 provides and adds a few that lend themselves well to being used with Java 8 lambda expressions and method references.

All JUnit Jupiter assumptions are static methods in the org.junit.jupiter.api.Assumptions class.

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static org.junit.jupiter.api.Assumptions.assumingThat;

import example.util.Calculator;

import org.junit.jupiter.api.Test;

class AssumptionsDemo {

	private final Calculator calculator = new Calculator();

	@Test
	void testOnlyOnCiServer() {
		assumeTrue("CI".equals(System.getenv("ENV")));
		// remainder of test
	}

	@Test
	void testOnlyOnDeveloperWorkstation() {
		assumeTrue("DEV".equals(System.getenv("ENV")),
			() -> "Aborting test: not on developer workstation");
		// remainder of test
	}

	@Test
	void testInAllEnvironments() {
		assumingThat("CI".equals(System.getenv("ENV")),
			() -> {
				// perform these assertions only on the CI server
				assertEquals(2, calculator.divide(4, 2));
			});

		// perform these assertions in all environments
		assertEquals(42, calculator.multiply(6, 7));
	}

}
It is also possible to use methods from JUnit 4’s org.junit.Assume class for assumptions. Specifically, JUnit Jupiter supports JUnit 4’s AssumptionViolatedException to signal that a test should be aborted instead of marked as a failure.

Exception Handling

JUnit Jupiter provides robust support for handling test exceptions. This includes the built-in mechanisms for managing test failures due to exceptions, the role of exceptions in implementing assertions and assumptions, and how to specifically assert non-throwing conditions in code.

Uncaught Exceptions

In JUnit Jupiter, if an exception is thrown from a test method, a lifecycle method, or an extension and not caught within that test method, lifecycle method, or extension, the framework will mark the test or test class as failed.

Failed assumptions deviate from this general rule.

In contrast to failed assertions, failed assumptions do not result in a test failure; rather, a failed assumption results in a test being aborted.

See Assumptions for further details and examples.

In the following example, the failsDueToUncaughtException() method throws an ArithmeticException. Since the exception is not caught within the test method, JUnit Jupiter will mark the test as failed.

private final Calculator calculator = new Calculator();

@Test
void failsDueToUncaughtException() {
	// The following throws an ArithmeticException due to division by
	// zero, which causes a test failure.
	calculator.divide(1, 0);
}
It’s important to note that specifying a throws clause in the test method has no effect on the outcome of the test. JUnit Jupiter does not interpret a throws clause as an expectation or assertion about what exceptions the test method should throw. A test fails only if an exception is thrown unexpectedly or if an assertion fails.

Failed Assertions

Assertions in JUnit Jupiter are implemented using exceptions. The framework provides a set of assertion methods in the org.junit.jupiter.api.Assertions class, which throw AssertionError when an assertion fails. This mechanism is a core aspect of how JUnit handles assertion failures as exceptions. See the Assertions section for further information about JUnit Jupiter’s assertion support.

Third-party assertion libraries may choose to throw an AssertionError to signal a failed assertion; however, they may also choose to throw different types of exceptions to signal failures. See also: Third-party Assertion Libraries.
JUnit Jupiter itself does not differentiate between failed assertions (AssertionError) and other types of exceptions. All uncaught exceptions lead to a test failure. However, Integrated Development Environments (IDEs) and other tools may distinguish between these two types of failures by checking whether the thrown exception is an instance of AssertionError.

In the following example, the failsDueToUncaughtAssertionError() method throws an AssertionError. Since the exception is not caught within the test method, JUnit Jupiter will mark the test as failed.

private final Calculator calculator = new Calculator();

@Test
void failsDueToUncaughtAssertionError() {
	// The following incorrect assertion will cause a test failure.
	// The expected value should be 2 instead of 99.
	assertEquals(99, calculator.add(1, 1));
}

Asserting Expected Exceptions

JUnit Jupiter offers specialized assertions for testing that specific exceptions are thrown under expected conditions. The assertThrows() and assertThrowsExactly() assertions are critical tools for validating that your code responds correctly to error conditions by throwing the appropriate exceptions.

Using assertThrows()

The assertThrows() method is used to verify that a particular type of exception is thrown during the execution of a provided executable block. It not only checks for the type of the thrown exception but also its subclasses, making it suitable for more generalized exception handling tests. The assertThrows() assertion method returns the thrown exception object to allow performing additional assertions on it.

@Test
void testExpectedExceptionIsThrown() {
	// The following assertion succeeds because the code under assertion
	// throws the expected IllegalArgumentException.
	// The assertion also returns the thrown exception which can be used for
	// further assertions like asserting the exception message.
	IllegalArgumentException exception =
		assertThrows(IllegalArgumentException.class, () -> {
			throw new IllegalArgumentException("expected message");
		});
	assertEquals("expected message", exception.getMessage());

	// The following assertion also succeeds because the code under assertion
	// throws IllegalArgumentException which is a subclass of RuntimeException.
	assertThrows(RuntimeException.class, () -> {
		throw new IllegalArgumentException("expected message");
	});
}
Using assertThrowsExactly()

The assertThrowsExactly() method is used when you need to assert that the exception thrown is exactly of a specific type, not allowing for subclasses of the expected exception type. This is useful when precise exception handling behavior needs to be validated. Similar to assertThrows(), the assertThrowsExactly() assertion method also returns the thrown exception object to allow performing additional assertions on it.

@Test
void testExpectedExceptionIsThrown() {
	// The following assertion succeeds because the code under assertion throws
	// IllegalArgumentException which is exactly equal to the expected type.
	// The assertion also returns the thrown exception which can be used for
	// further assertions like asserting the exception message.
	IllegalArgumentException exception =
		assertThrowsExactly(IllegalArgumentException.class, () -> {
			throw new IllegalArgumentException("expected message");
		});
	assertEquals("expected message", exception.getMessage());

	// The following assertion fails because the assertion expects exactly
	// RuntimeException to be thrown, not subclasses of RuntimeException.
	assertThrowsExactly(RuntimeException.class, () -> {
		throw new IllegalArgumentException("expected message");
	});
}

Asserting That no Exception is Expected

Although any exception thrown from a test method will cause the test to fail, there are certain use cases where it can be beneficial to explicitly assert that an exception is not thrown for a given code block within a test method. The assertDoesNotThrow() assertion can be used when you want to verify that a particular piece of code does not throw any exceptions.

@Test
void testExceptionIsNotThrown() {
	assertDoesNotThrow(() -> {
		shouldNotThrowException();
	});
}

void shouldNotThrowException() {
}