DEV Community

loading...
Cover image for Migrating from JUnit 4 to JUnit 5 with Spring Boot 2.3.x

Migrating from JUnit 4 to JUnit 5 with Spring Boot 2.3.x

ecirilo profile image Edgar Cirilo ・3 min read

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>
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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...
}
Enter fullscreen mode Exit fullscreen mode

@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...
}
Enter fullscreen mode Exit fullscreen mode

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...
}
Enter fullscreen mode Exit fullscreen mode

Is now

@ExtendWith(MockitoExtension.class)
public class TheBestServiceTest {
    // class body...
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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());
}
Enter fullscreen mode Exit fullscreen mode

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.

Discussion

pic
Editor guide