JUnit Jupiter best practices
After migrating to JUnit 5, there are several common patterns that should be cleaned up to make tests more modern and maintainable.
Remove test prefix
In JUnit 3, test methods had to start with test
because the framework used reflection to discover tests by name.
JUnit 4 and 5 use annotations, so this prefix is no longer necessary and just adds noise.
- Before
- After
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CalculatorTest {
@Test
void testAddition() {
assertEquals(4, 2 + 2);
}
@Test
void testSubtraction() {
assertEquals(0, 2 - 2);
}
@Test
void testMultiplication() {
assertEquals(4, 2 * 2);
}
}
The test
prefix is a remnant from JUnit 3 and adds unnecessary noise to test method names.
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CalculatorTest {
@Test
void addition() {
assertEquals(4, 2 + 2);
}
@Test
void subtraction() {
assertEquals(0, 2 - 2);
}
@Test
void multiplication() {
assertEquals(4, 2 * 2);
}
}
Removing the test
prefix makes method names cleaner and more readable. The @Test
annotation already indicates it's a test method.
Remove public modifier
JUnit 4 required test classes and methods to be public, but JUnit 5 removed this requirement. Package-private (default) visibility is sufficient and reduces boilerplate.
- Before
- After
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class CalculatorTest {
private Calculator calculator;
@BeforeEach
public void setUp() {
calculator = new Calculator();
}
@Test
public void addition() {
assertEquals(4, calculator.add(2, 2));
}
@Test
public void subtraction() {
assertEquals(0, calculator.subtract(2, 2));
}
}
The public
modifiers on the class and test methods are unnecessary in JUnit 5 and add visual clutter.
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CalculatorTest {
private Calculator calculator;
@BeforeEach
void setUp() {
calculator = new Calculator();
}
@Test
void addition() {
assertEquals(4, calculator.add(2, 2));
}
@Test
void subtraction() {
assertEquals(0, calculator.subtract(2, 2));
}
}
Package-private visibility is cleaner and sufficient for JUnit 5 test classes and methods.
Only keep public
if you need the test class to be accessible from other packages.
Apply best practices
Beyond basic cleanup, there are many other best practices to apply when modernizing tests.
- Before
- After
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
class UserServiceTest {
@Test
void testUserCreation() {
UserService service = new UserService();
User user = service.createUser("John", "Doe");
assertNotNull(user);
assertEquals("John", user.getFirstName());
assertEquals("Doe", user.getLastName());
assertTrue(user.getId() > 0);
}
}
While this test works, it could be improved with better naming, display names, and more expressive assertions.
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@DisplayName("User Service")
class UserServiceTest {
@Test
@DisplayName("creates user with first and last name")
void createUser() {
UserService service = new UserService();
User user = service.createUser("John", "Doe");
assertThat(user)
.isNotNull()
.extracting(User::getFirstName, User::getLastName, User::getId)
.containsExactly("John", "Doe", 1L);
}
}
Modern tests should:
- Use descriptive method names without
test
prefix - Add
@DisplayName
for human-readable test reports - Use AssertJ for more expressive assertions
- Chain related assertions together
- Follow the Arrange-Act-Assert pattern
Automated modernization
OpenRewrite provides recipes to automatically apply these modernizations.
- Moderne CLI
- Maven Command Line
- Maven POM
- Gradle init script
- Gradle
- IntelliJ IDEA Ultimate
The Moderne CLI allows you to run OpenRewrite recipes on your project without needing to modify your build files, against serialized Lossless Semantic Tree (LST) of your project for a considerable performance boost & across projects.
You will need to have configured the Moderne CLI on your machine before you can run the following command.
- If project serialized Lossless Semantic Tree is not yet available locally, then build the LST. This is only needed the first time, or after extensive changes:
mod build ~/workspace/
- If the recipe is not available locally yet, then you can install it once using:
mod config recipes jar install org.openrewrite.recipe:rewrite-testing-frameworks:LATEST
- Run the recipe.
mod run ~/workspace/ --recipe org.openrewrite.java.testing.junit5.JUnit5BestPractices
You will need to have Maven installed on your machine before you can run the following command.
mvn -U org.openrewrite.maven:rewrite-maven-plugin:run -Drewrite.recipeArtifactCoordinates=org.openrewrite.recipe:rewrite-testing-frameworks:RELEASE -Drewrite.activeRecipes=org.openrewrite.java.testing.junit5.JUnit5BestPractices -Drewrite.exportDatatables=true
You may add the plugin to your pom.xml
file, so that it is available for all developers and CI/CD pipelines.
- Add the following to your
pom.xml
file:
<project>
<build>
<plugins>
<plugin>
<groupId>org.openrewrite.maven</groupId>
<artifactId>rewrite-maven-plugin</artifactId>
<version>LATEST</version>
<configuration>
<exportDatatables>true</exportDatatables>
<activeRecipes>
<recipe>org.openrewrite.java.testing.junit5.JUnit5BestPractices</recipe>
</activeRecipes>
</configuration>
<dependencies>
<dependency>
<groupId>org.openrewrite.recipe</groupId>
<artifactId>rewrite-testing-frameworks</artifactId>
<version>LATEST</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</project>
- Run the recipe.
mvn rewrite:run
Gradle init scripts are a good way to try out a recipe without modifying your build.gradle
file.
- Create a file named
init.gradle
in the root of your project.
initscript {
repositories {
maven { url "https://plugins.gradle.org/m2" }
}
dependencies { classpath("org.openrewrite:plugin:latest.release") }
}
rootProject {
plugins.apply(org.openrewrite.gradle.RewritePlugin)
dependencies {
rewrite("org.openrewrite.recipe:rewrite-testing-frameworks:latest.release")
}
rewrite {
activeRecipe("org.openrewrite.java.testing.junit5.JUnit5BestPractices")
setExportDatatables(true)
}
afterEvaluate {
if (repositories.isEmpty()) {
repositories {
mavenCentral()
}
}
}
}
- Run the recipe.
gradle --init-script init.gradle rewriteRun
You can add the plugin to your build.gradle
file, so that it is available for all developers and CI/CD pipelines.
- Add the following to your
build.gradle
file:
plugins {
id("org.openrewrite.rewrite") version("latest.release")
}
rewrite {
activeRecipe("org.openrewrite.java.testing.junit5.JUnit5BestPractices")
setExportDatatables(true)
}
repositories {
mavenCentral()
}
dependencies {
rewrite("org.openrewrite.recipe:rewrite-testing-frameworks:latest.release")
}
- Run
gradle rewriteRun
to run the recipe.
You can run OpenRewrite recipes directly from IntelliJ IDEA Ultimate, by adding a rewrite.yml
file to your project.
---
type: specs.openrewrite.org/v1beta/recipe
name: com.github.timtebeek.AdoptJUnitJupiter
displayName: JUnit Jupiter best practices
description: Apply JUnit Jupiter best practices to assertions.
recipeList:
- org.openrewrite.java.testing.junit5.JUnit5BestPractices
After adding the file, you should see a run icon in the left margin offering to run the recipe.
OpenRewrite recipes automatically handle:
- RemoveTestPrefix - Removes
test
prefix from method names - TestsShouldNotBePublic - Removes unnecessary
public
modifiers - JUnit5BestPractices - Applies various JUnit 5 best practices