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.
-
For Gradle and Java, check out the
junit-jupiter-starter-gradleproject. -
For Gradle and Kotlin, check out the
junit-jupiter-starter-gradle-kotlinproject. -
For Gradle and Groovy, check out the
junit-jupiter-starter-gradle-groovyproject. -
For Maven, check out the
junit-jupiter-starter-mavenproject. -
For Ant, check out the
junit-jupiter-starter-antproject.
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.
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
@Testannotation, 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
@Nestedtest 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@TestFactorymethod 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@TestFactorymethod 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@TestFactorymethods in the current class; analogous to JUnit 4’s@BeforeClass. Such methods are inherited unless they are overridden and must bestaticunless the "per-class" test instance lifecycle is used. @AfterAll-
Denotes that the annotated method should be executed after all
@Test,@RepeatedTest,@ParameterizedTest, and@TestFactorymethods in the current class; analogous to JUnit 4’s@AfterClass. Such methods are inherited unless they are overridden and must bestaticunless 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,
@BeforeAlland@AfterAllmethods cannot be used directly in a@Nestedtest class unless the "per-class" test instance lifecycle is used. Beginning with Java 16,@BeforeAlland@AfterAllmethods can be declared asstaticin a@Nestedtest 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.iopackage. 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
@Testmethod).
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,
staticmember class, or@Nestedclass that contains at least one test method, i.e. a container. Test classes must not beabstractand must have a single constructor. Javarecordclasses 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 It is generally recommended to omit the |
|
Field and method inheritance
Fields in test classes are inherited. For example, a Test methods and lifecycle methods are inherited unless they are overridden according to
the visibility rules of the Java language. For example, a See also: Field and Method Search Semantics |
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.
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.
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 |
|---|---|
|
Matches the standard display name generation behavior in place since JUnit Jupiter 5.0 was released. |
|
Extends the functionality of |
|
Replaces underscores with spaces. |
|
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:
-
value of the
@DisplayNameannotation, if present -
by calling the
DisplayNameGeneratorspecified in the@DisplayNameGenerationannotation, if present -
by calling the default
DisplayNameGeneratorconfigured via the configuration parameter, if present -
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 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 Similar side effects may be encountered with other frameworks that rely on
|
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.TestAbortedExceptionto 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() {
}