# JUnit 5 Jupiter Quick Start Tutorial

JUnit 5 (opens new window) is the first version of JUnit which separates the API for discovering and running test (called JUnit Platform) from the API for writing tests (called JUnit Jupiter)

  • JUnit Jupiter is the API used by developers for writing unit tests. This API contains annotations (like @Test) and assertions (like assertEquals), called the programming model but also an Extension API, called the extension model.
  • JUnit Platform contains
    • the Launcher API used by IDEs and build tools (like graddle and maven) to discover and execute tests and
    • the Test Engine API used by third party library (think JUnit Jupiter, Cucumber, FitNesse and JUnit 4) to allow their tests to run on the platform.

The job done by the JUnit team for this version is impressive. Let's take a closer look.

# Maven Installation

First, include the JUnit Jupiter Engine as a test dependency in your maven configuration (you can find the latest version on mvnrepository (opens new window)):

<dependencies>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-engine</artifactId>
        <version>5.4.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.4.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

These dependencies include both the JUnit Jupiter Engine and an implementation of the Jupiter platform test engine. With these dependencies, you can write test classes, annotate their methods with @Test and use the Jupiter assertion and assumption methods.

TIP

If you are an IntelliJ IDEA (opens new window) user, junit-jupiter-api dependency is enough (opens new window) to run your tests from IDEA. IDEA automatically deduce from this dependency the corresponding JUnit Platform (launcher and test engines) required to discover and execute your tests.

# Running your test with Maven

# Using Surefire plugin

JUnit 5 is now well integrated with the Maven Surefire Plugin (opens new window). Starting from version 2.22.0, no special configuration is required for running JUnit Jupiter tests with mvn test. Just add the Surefire Plugin in the maven build section:

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.22.0</version>
        </plugin>
    </plugins>
</build>
1
2
3
4
5
6
7
8
9

Before version 2.22.0 however, the glue between Maven Surefire Plugin and the JUnit Platform wasn't part of the Maven Surefire Plugin. That's why versions < 2.22.0 require junit-platform-surefire-provider as dependency (here the JIRA for the code donation (opens new window) between the two projects):

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.21.0</version>
            <dependencies>
                <dependency>
                    <groupId>org.junit.platform</groupId>
                    <artifactId>junit-platform-surefire-provider</artifactId>
                    <version>1.2.0</version>
                </dependency>
            </dependencies>
        </plugin>
    </plugins>
</build>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

By default, all classes which name match Test*, *Test, *Tests or *TestCase will be included. If you have a different naming convention, this behavior can be configured (opens new window).

# Using Failsafe plugin

JUnit 5 is also well integrated with the Maven Failsafe Plugin (opens new window) for running integration test. Here again, just add the plugin to your maven build configuration:

<build>
    <plugins>
        <plugin>
            <artifactId>maven-failsafe-plugin</artifactId>
            <version>2.22.0</version>
        </plugin>
    </plugins>
</build>
1
2
3
4
5
6
7
8

By default, all classes which name match IT*, *IT or *ITCase will be included. This can be configured (opens new window) if you have a different naming convention for your test classes.

# Writing Tests

# Simple Test Class

To write a test class, just ensure its name match the inclusion pattern of your test runner. From there, each method annotated with org.junit.jupiter.api.Test will become part of your tests:

package com.piecesofcode.quickstart.junit5;

import org.junit.jupiter.api.Test;

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

public class SomethingToTest {

    @Test
    public void dummyTest() {
        assertTrue(true);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# Display Name

Jupiter introduce the @DisplayName annotation to help developers describe their tests:

@Test
@DisplayName("Showcase boolean assertion on USA country name ☺")
public void poorTestName() {
  assertTrue(Country.USA.getName().matches("U.*S.*A.*"));
  assertFalse(() -> Country.USA.getName().isEmpty(), "Java 8 lambda can be used ");
  System.out.println('\u263A');
}
1
2
3
4
5
6

TIP

With @DisplayName, the displayed test name is not the method name anymore (like it was in JUnit 4) but the string passed as parameter to the annotation. Hence, it's now possible to use space and special characters (and if it's still not enough, you can also add some emoji 😮)

# Assertions

Jupiter comes with various built-in assertion methods to express your test cases. They’re all static methods in org.junit.jupiter.api.Assertions class so they can be imported via a import static.

TIP

Jupiter assertion can also be replaced by other third-party library, like assertj (opens new window) or hamcrest (opens new window).

# Boolean assertion

@Test
public void booleanAssertion() {
  assertTrue(Country.USA.getName().matches("U.*S.*A.*"));
  assertFalse(() -> Country.USA.getName().isEmpty(), "Java 8 lambda can be used");
}
1
2
3
4

# Equality assertion

@Test
public void equalityAssertion() {
  assertEquals(UNITED_STATES_OF_AMERICA, Country.USA.getName());
  assertEquals(
      UNITED_STATES_OF_AMERICA,
      Country.USA.getName(),
      "Assertion can display a message when they fail.");
  assertEquals(36.0f, Country.USA.getDensity(), 0.1f);
  Lists.list(Country.USA, Country.CHINA, Country.AUSTRALIA, Country.FRANCE)
      .stream()
      .map(Country::getName)
      .forEach(countryName -> assertFalse(countryName::isEmpty));
}
1
2
3
4
5
6
7
8
9
10
11
12

TIP

When testing float equality, give a delta to account for floating-point number imprecision.

# Exception assertion

You can also expect exception via assertThrows. Notice here that assertThrows return the expected exception, allowing to assert on its property.

@Test
public void assertionOnExceptions() {
  Country noCountryForOldMan = null;
  Throwable exception =
      assertThrows(NullPointerException.class, () -> noCountryForOldMan.getPopulation());
  assertNull(exception.getCause());
}
1
2
3
4
5
6

# Grouping assertion

If you have several assertions in the same test method, your test will stop at the first failing assertion, without executing the following ones. If you want to report all failures, you can use assertAll. assertAll fails if any of its assertion fails, but execute all assertions, thus reporting all failures.

@Test
public void assertionGroup() {
  assertAll(
      () -> assertEquals(UNITED_STATES_OF_AMERICA, Country.USA.getName()),
      () -> assertEquals("Washington, D.C.", Country.USA.getCapital()));
}
1
2
3
4
5

# Lifecycle Annotations

The @BeforeEach and @AferEach annotations are used to execute code before (resp. after) each test method.

@BeforeEach
public void initTest() {
  LOG.debug("Called before each test");
}
1
2
3
@AfterEach
public void cleanTest() {
  LOG.debug("Called after each test");
}
1
2
3

Likewise, @BeforeAll and @AfterAll are used to execute code before (resp. after) all test methods (hence, only once per test classes).

@BeforeAll
public static void setUp() {
  LOG.debug("Called once before all tests");
}
1
2
3
@AfterAll
public static void tearDown() {
  LOG.debug("Called once after all test");
}
1
2
3

WARNING

Notice that @BeforeAll and @AfterAll are static methods. If you don't declare @BeforeAll and @AfterAll as such, you will see this exception:

org.junit.platform.commons.JUnitException: @BeforeAll method 'public void ...' 
must be static unless the test class is annotated with 
@TestInstance(Lifecycle.PER_CLASS).
1
2
3

That's because, by default, JUnit will instantiate the test class for each test method. Hence, anything that need to be shared between tests should be done at the class level. You can change this behaviour by adding @TestInstance(Lifecycle.PER_CLASS) to your test class.

# Conditionnal testing

Sometimes test should be run only under some conditions. Jupiter implements two ways of dealing with this: the programmatic way, via assumptions, and the declarative way via annotation and the extension model.

# Conditional testing via Assumptions

Assumptions are the programmatic way to run test conditionally. Assumptions are static methods in org.junit.jupiter.api.Assumptions class so they can be imported via a import static.

@Test
public void assumption() {
  CountryProvider provider = new GeoNameCountryProvider();
  Countries countries = provider.provides();
  assumeTrue(countries != null && !countries.asList().isEmpty());
  assertTrue(countries.asList().contains(Country.FRANCE));
}
1
2
3
4
5
6

In the above example, if the provider fails to return any countries, the test will be aborted (and reported as such) but will not fail. However, if the provider successfully returns some countries, the test continue and execute the remaining assertion.

It's also possible to execute only part of a test under some assumptions via assumeThat. In the example below, assertEquals(UNITED_STATES_OF_AMERICA, Country.USA.getName()); will always be executed but assertTrue(countries.asList().contains(Country.USA)); will only be executed if the provider returns some countries:

@Test
public void partialAssumption() {
  CountryProvider provider = new GeoNameCountryProvider();
  Countries countries = provider.provides();
  assumingThat(
      countries != null && !countries.asList().isEmpty(),
      () -> assertTrue(countries.asList().contains(Country.USA)));
  assertEquals(UNITED_STATES_OF_AMERICA, Country.USA.getName());
}
1
2
3
4
5
6
7
8

# Conditional testing via Annotations

Jupiter also provides a handful of annotation to enable/disable test based on some external condition (available in org.junit.jupiter.api.condition package). The most basic one is @Disable which allow you to disable a test.

@Test
@Disabled
public void disabledTest() {
  LOG.debug("This code is not executed");
}
1
2
3
4

Other built-in conditions include:

  • Os: @EnabledOnOs or @DisabledOnOs enable/disable tests depending on the OS they are running on.
  • Jre (java version): @EnabledOnJre or @DisabledOnJre enable/disable tests depending on the JRE version they are running on.
  • System Property: @EnabledIfSystemProperty or @DisabledIfSystemProperty enable/disable tests depending on a system property.
  • Environment Variable: @EnabledIfEnvironmentVariable or @EnabledIfEnvironmentVariable enable/disable tests depending on an environment variable.

All these annotations can be placed on the test class or test method that require to be run conditionally:

@Test
@EnabledIfSystemProperty(named = "user.language", matches = ".*en.*")
public void testOnlyIfUserLanguageIsEnglish() {
  System.getProperties().forEach((key, value) -> LOG.debug("{}:{}", key, value));
}
1
2
3
4

TIP

@EnabledIfSystemPropertyand @EnabledIfEnvironmentVariable takes two parameters, named for the name of the system property (resp. environment variable) and matches for its value. This last parameter is evaluated as a regex by Jupiter, wild cards are thus allowed.

Last updated: 6/11/2019, 11:01:06 PM