Skip to content

Migrating from JUnit 4

Estimated time to read: 6 minutes

Differences between JUnit 4 and JUnit 5

JUnit 5's API Package

JUnit 5's API uses a new package; org.junit.jupiter

All relevant classes and annotations for JUnit 5 are within this single package. This aids in enabling a project containing tests from different JUnit versions without conflicts.

Public Classes / Methods

In JUnit 4, test methods and classes have to be declared as public.

In JUnit 5, classes and methods can have package visibility. Classes and methods are no longer required to be public

Lifecycle Annotations

JUnit 4 Annotation JUnit5 Annotation Description
@BeforeClass @BeforeAll Used to declare the methods to run before tests are executed.
@Before @BeforeEach Used to declare the methods to run before each individual test is executed.
@After @AfterEach Used to declare the methods to run after each individual test is executed.
@AfterClass @AfterAll Used to declare the methods to run after all tests have been executed.

Note

In JUnit 5, the public modifier is no longer required.

@BeforeClass
public static void setUpOnce() { // ... }
@BeforeAll
static void setUpOnce() { // ... }
@BeforeAll
void setUpOnce() { // ... }

Ignore / Disable

In JUnit 4, the @Ignore annotation is used to ignore a test

JUnit4
@Test
@Ignore
public static void aTest() { ... }

In JUnit 5, @Ignore has been replaced with @Disable

JUnit5
@Test
@Disable
public void aTest() { ... }

@Disable can be used in JUnit 5, however, this is reliant on the junit-jupiter-migrationsupport dependency.

JUnit5
@EnableJUnit4MigrationSupport
// or // @ExtendWith(IgnoreCondition.class)
class IgnoredTestsDemo {
 @Ignore
 @Test
 void aTest() { ... }
}

Categories / Tags

In JUnit 4, the @Category annotation is used to organise tests classes and methods into categories. Classes and Interfaces are used as categories, whilst sub-classes are used as sub-categories.

JUnit4
@Test
@Category({ Version1.class, Important.class })
void aTest() { ... }

In JUnit 5, the @Tag annotation replaces @Category. A string is passed as a tag identifier. However, there is no support for sub-typing.

JUnit5
@Test
@Tag("Version1")
@Tag("Important")
void aTest() { ... }

Assertion Methods

In JUnit 4, the assertions method package can be found at org.junit.Assert.

In JUnit 5, the assertion method package now belong to the JUnit Jupiter project and can now be found at org.junit.jupiter.api.Assertions

Assertion Parameter Order

In JUnit 4, the first parameter of an assert method is the error message, followed by the expected and then the actual values.

assertEquals("Error Message", expected, actual)

In JUnit 5, the expected value comes first, followed by the actual value, and finally an optional error message.

assertEquals(expected, actual, "Optional Error Message")

Lambda expressions can also be used with JUnit 5 to provide Lazy Strings support. To only create an error message when needed.

assertEquals(expected, actual, () -> "Error Message"

assertThat

In JUnit 4, the assertThat method takes a Hamcrest matcher, as a result, Hamcrest is always included as a dependency of JUnit 4.

In JUnit 5, assertThat has been removed. To use this method, Hamcrest must be added as a dependency explicitly and used directly from it.

Continuing Tests

In JUnit 4, to continue the execution of a test after an assertion failure, an error collector has to be defined.

JUnit4
@Rule
ErrorCollector collector = new ErrorCollector();

@Test
public void aTest() {
 collector.checkThat("aa", equalTo("a"));
 collector.checkThat(1, equalTo(11));
}

In JUnit 5, the @Rule annotation has been removed. Instead the assertAll method can be used. This takes a variable number of lambda expressions, that wrap each assertion method to execute the null and aggregate all the possible errors.

JUnit5
@Test
void aTest() {
 assertAll (
 () -> assertThat("aa", equalTo("a")),
 () -> assertThat(1, equalTo(11))
 );
}

Timeouts

In JUnit 4, timeouts can be specified to specific methods or to all methods in a class.

JUnit4
@Test(timeout=1000)
public void testWithTimeout() { ... }
JUnit4
@Rule
public Timeout globalTimeout = Timeout.seconds(10);

@Test
public void aTest() { ... }

In JUnit 5, the assertTimeout method replaces the timeout argument in JUnit 4. assertTimeout takes the timeout value as a duration object and a lambda expression.

JUnit5
@Test
void aTest() {
 assertTimeout(ofSeconds(10), () -> {
  // ...
 }, "The method took more than 10 seconds to run.");
}

Alternatively, the assertTimeoutPreemptively method can be used to abort the lambda expression execution if it takes longe than the specific duration.

JUnit5
@Test
void aTest() {
 assertTimeoutPreemptively(ofSeconds(10), () -> {
  // ...
 }, "The method took more than 10 seconds to run. Aborted!");
}

There is also a @Timeout annotation, that can be used to declare that a Test, TestFactory, TestTemplate or Lifecycle method should fail if its execution takes longer than the specified duration.

JUnit5
@Test
@Timeout
// @Timeout(value = 10, unit = TimeUnit.Seconds)
void aTest() { ... }

Exception Testing

In JUnit 4, there are three ways to see if an exception has been thrown.

JUnit4_TryCatch
@Test
public void catchTheException() {
 try {
  // Code that may throw an exception
  fail("We shouldn't hit this if programmed correctly!")
  } catch(RuntimeException e) {
  // Assert something about the exception
 }
}
JUnit_Annotation
@Test(expected = RuntimeException.class)
public void annotationBasedApproach() {
 // Code that may throw an exception
}
JUnit4_Rule
@Rule
ExpectedException thrown = ExpectedException.none();

@Test
public void ruleBasedApproach() {
 thrown.expect(RuntimeException.class);
 thrown.expectMessage(containsString("..."));
  // Code that may throw an exception
}

In JUnit 5, the assertThrows method can be used to declare that the code of a lambda expression can thrown an exception.

JUnit5
@Test
void newAssertThrows() {
 assertThrows(Runtime.Exception.class, () -> {
  // Code that may throw an exception
 });
}
JUnit5
@Test
void newAssertThrows() {
 RuntimeException e = assertThrows(RuntimeException.class,
           () -> {
             // Code that may throw an exception
           });
 assertEquals("...", e.getMessage());
}

Extension Model

In JUnit 4, the extension model is based on runners, rules and class rules.

JUnit4
@RunWith(Suite.class)
public class aTest { ... }

@Rule
public final TemporaryFolder folder - new TemporaryFolder();

@ClassRule
public static final ExternalResource resource = new ExternalResource() { ... }

In JUnit 5, there is a greater focus on extensibility. The extension model is now based on interfaces that can be implemented to control certain extension points.

JUnit 5
class AnExtension implements BeforeTestExecutionCallback {
 // ...
}

@ExtendWith(AnExtension.class)
void aTest() { ... }

New features

JUnit 5 includes the following new features:

  • Nested Tets
  • Custom display names
  • Java 8 support
  • Parameter injection
  • Dynamic and parameterised tests
  • Meta-annotations

Running Junit 4 Tests in JUnit 5

JUnit 5 provides an easy upgrade path from JUnit 4.

Unless there is a compelling reason to do so, JUnit 4 tests are not required to be re-written to support JUnit 5 as JUnit 5 is backward compatible with JUnit 4 via the JUnit Vintage engine.

JUnit 4 and 5 reside in different packages, this allows having both JUnit 4 and 5 libraries in the CLASSPATH at the same time.

Usage

Both JUnit 4 and JUnit 5 have to be added as dependencies to the project.

build.gradle
// ...

dependencies {
 testImplementation(platform('org.junit:junit-bom:5.x.x'))
 testImplementation('org.junit.jupiter:junit-jupiter')

 testImplementation('junit:junit:4.13.2')
 testRuntimeOnly('org.junit.vintage:junit-vintage-engine')
}

// ...
pom.xml
// ... 
 <dependencies>
  <dependency>
   <groupId>org.junit.jupiter</groupId>
   <artifactId>junit-jupiter</artifactId>
   <scope>test</scope>
  </dependency>
  <dependency>
   <groupId>junit</groupId>
   <artifactId>junit</artifactId>
   <version>4.13.2</version>
   <scope>test</scope>
  </dependency>
  <dependency>
   <groupId>org.junit.vintage</groupId>
   <artifactId>junit-vintage-engine</artifactId>
   <scope>test</scope>
  </dependency>
 </dependencies>

// ...

By default, no additional configuration is required and no changes are required to be made within the project code. JUnit 4 tests will be executed with the junit-vintage engine and JUnit 5 tests will be executed with the junit-jupiter engine.

Rule Support in JUnit 5

JUnit 4 Extension Mechanism

JUnit 4 has had many extensions created for it over its lifetime. The JUnit 4 Extension Mechanism offers:

  • Runners
  • Annotated with @RunWith
  • Allows almost every aspect of test execution can be changed. There can only be one runner per test class.
  • Rules
  • Annotated with @Rule
  • Introduced in JUnit 4.7.
  • Rules wrap the execution of a test method, before and after the test execution
  • Requires a public, non-static field in the test class. The type of the field must be a sub-type of TestRule.
  • Some rules can affect operation of the whole class

JUnit 5 Extension Mechanism

In JUnit 5, the Extension Mechanism changed:

  • Extension points represented by Interfaces.
  • Runners are now implemented as extensions of a type
  • Limited @Rule support for select types can be achieved by the @EnableRuleMigrationSupport annotation, enabling:
  • ExternalResource
    • TemporaryFolder
  • Verifier
    • ErrorCollector
  • ExpectedException

JUnit 5 Migration Support API

Annotations for migration support are located in:

  • Group ID: org.junit.jupiter
  • Artifact ID: junit-jupiter-migrationsupport
  • Version: 5.x.x