Migrating from JUnit 4 to JUnit 5 with Spring Boot 2.3.x
Edgar Cirilo
Posted on October 27, 2020
Recently, our team has moved to the last release of Spring (2.3.4). So we are taking advantage to move some projects unit tests to JUnit 5 due this is the default version since Spring Boot 2.2.x. Even though Junit 5 includes a vintage engine to support the JUnit 3 and JUnit 4 versions, we decided to fully migrate to JUnit Jupiter.
Dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
Annotations
In the new version of JUnit, the base package has changed to org.junit.jupiter.api
and some annotations have changed. Below you will find a table with the mapping for the most used annotations.
JUnit 4 | JUnit 5 |
---|---|
org.junit.Test |
org.junit.jupiter.api.Test |
org.junit.Before |
org.junit.jupiter.api.BeforeEach |
org.junit.After |
org.junit.jupiter.api.AfterEach |
org.junit.BeforeClass |
org.junit.jupiter.api.BeforeAll |
org.junit.AfterClass |
org.junit.jupiter.api.AfterAll |
Here is the API Documentation for the full list of annotations.
Exceptions
When using JUnit 4, we used the ExpectedException rule. However, it is no longer supported by JUnit 5. Fortunately, the Assertions API included in the Jupiter package have a handy way to assert the exceptions.
The assertThrows()
method asserts that execution of the supplied executable throws an exception of the expectedType and returns the exception.
public static <T extends Throwable> T assertThrows(Class<T> expectedType, Executable executable)
Once the exception is returned, we can use the preferred Assertions API. In our case, we use AssertJ, so it's pretty similar to any other object.
@Test
public void exampleMethod_ThrowsCustomException_Fail() {
CustomException expectedException =
Assertions.assertThrows(CustomException.class, () -> {
service.doSomething(ObjectDTO dto);
});
assertThat(expectedException).isNotNull();
assertThat(expectedException.getErrorCode()).isEqualTo(50);
}
Spring
Spring provides some convenient annotations on behalf of JUnit 5.
@SpringJUnitConfig
combines @ExtendWith(SpringExtension.class)
and @ContextConfiguration
into a single annotation. So, we have access to the configuration options through the @SpringJUnitConfig
annotation.
@SpringJUnitConfig( classes = {TestConfig.class, SomeService.class})
public class SomeServiceTest {
// class body...
}
@SpringJUnitWebConfig
is pretty similar, with the difference that it includes @WebAppConfiguration
besides @ExtendWith(SpringExtension.class)
and @ContextConfiguration
@SpringJUnitWebConfig( classes = {WebConfig.class, SomeController.class})
public class SomeControllerTest {
@Autowired
private WebApplicationContext webAppContext;
// class body...
}
You can read the official documentation of spring and spring boot to see the full list of annotations and a full description of each one.
Mockito
For Mockito, there are few changes to take into account. For previous versions, we used @RunWith
but for JUnit 5 we have to use @ExtendWith
instead:
@RunWith(MockitoJUnitRunner.class)
public class TheBestServiceTest {
// class body...
}
Is now
@ExtendWith(MockitoExtension.class)
public class TheBestServiceTest {
// class body...
}
Statics
Sometimes we need to mock some static methods. Usually, we use Powermock for this purpose. Nevertheless, Powermock won't be ported to the new version of JUnit. Fortunately, Mockito includes support for statics since the 3.4.x version.
In this case, we need to add some extra dependencies since spring-boot-starter-test
only includes Mockito up to 3.3.3 version:
Dependencies
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
Usage
Now we can mock static methods! As a matter of fact, this works pretty much as the normal methods. Ee only need to use a generic class supplied by Mockito; MockedStatic<T>
, and use it within a try
clause:
try (MockedStatic<ClassWithStaticMethod> mockedStatic = Mockito.mockStatic(ClassWithStaticMethod.class)) {
mockedStatic.when(ClassWithStaticMethod::someStaticMethod).thenReturn(new SomeDTO());
}
Conclusion
I think JUnit 5 comes with a different approach than its past version, something quite interesting, which can help us improve our unit tests. Despite being a big change, JUnit 5 makes sure to have backward compatibility to make migration more manageable. However, I highly recommend taking that step whenever possible.
Posted on October 27, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.