Migrating from JUnit 4 to JUnit 5 with Spring Boot 2.3.x
Tags: java, springboot, junit, testing
Published: 2020-10-27 16:13:59

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
```xml
<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](https://junit.org/junit5/docs/5.0.1/api/org/junit/jupiter/api/package-summary.html) 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.

```java
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.
```java
@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.

```java
@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`

```java
@SpringJUnitWebConfig( classes = {WebConfig.class, SomeController.class}) 
public class SomeControllerTest {

    @Autowired
    private WebApplicationContext webAppContext;
    // class body...
}
```
You can read the official documentation of [spring](https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/testing.html#integration-testing-annotations-junit-jupiter) and [spring boot](https://docs.spring.io/spring-boot/docs/2.3.4.RELEASE/reference/htmlsingle/#boot-features-testing) 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:

```java
@RunWith(MockitoJUnitRunner.class)
public class TheBestServiceTest {
    // class body...
}
```
Is now
```java
@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
```xml
<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:

```java
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.