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.
Ignore / Disable¶
In JUnit 4, the @Ignore
annotation is used to ignore a test
In JUnit 5, @Ignore
has been replaced with @Disable
@Disable
can be used in JUnit 5, however, this is reliant on the junit-jupiter-migrationsupport
dependency.
@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.
In JUnit 5, the @Tag
annotation replaces @Category
. A string is passed as a tag identifier. However, there is no support for sub-typing.
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.
@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.
@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.
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.
@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.
@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.
Exception Testing¶
In JUnit 4, there are three ways to see if an exception has been thrown.
@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
}
}
@Test(expected = RuntimeException.class)
public void annotationBasedApproach() {
// Code that may throw an exception
}
@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.
@Test
void newAssertThrows() {
assertThrows(Runtime.Exception.class, () -> {
// Code that may throw an exception
});
}
@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.
@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.
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.
// ...
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')
}
// ...
// ...
<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