Spring Boot 2 + JUnit 5 + Mockito

February 04, 2021 No comments Spring Boot Mockito JUnit5 Testing MockMvc SpringBootTest TestRestTemplate

1. Introduction

In this article, we will learn how to create a JUnit 5 test classes with Mockito in Spring Boot application. JUnit is one of the most popular testing frameworks for Java applications. JUnit 5 supports all modern features from Java 8 and allows using many different approaches and styles in testing.

2. Maven dependencies

Fortunately, the spring-boot-starter-test dependency from version 2.2.0 already comes with Junit 5 and contains also Hamcrest, and Mockito libraries for testing. That's good news because we don't need to add a lot of dependecy to our final pom.xml file.

The JUnit 5 is composed of three sub-projects, such as:

  • JUnit Platform - for launching testing frameworks on the JVM,
  • JUnit Jupiter - for writing tests and extensions in JUnit 5,
  • JUnit Vintage - provides a TestEngine for running JUnit 3 and JUnit 4 based tests on the platform.

For our case, we don't need JUnit 3 and JUnit 4 support that's why we will exclude this dependency.

The final pom.xml has the following structure:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.frontbackend.springboot</groupId>
    <artifactId>spring-boot2-junit5-mockito</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <!-- Inherit defaults from Spring Boot -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.1</version>
    </parent>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <!-- Add typical dependencies for a web application -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <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>
    </dependencies>

    <!-- Package as an executable jar -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

We have two main dependecies:

  • spring-boot-starter-web - web container is used for creating Spring REST API,
  • spring-boot-starter-test - main dependencies for unit and intergration testing.

The spring-boot-maven-plugin is used to create an executable jar with the Spring Boot application.

3. Project structure

The testing project has the following structure:

├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── frontbackend
│   │   │           └── springboot
│   │   │               ├── Application.java
│   │   │               ├── controller
│   │   │               │   └── HelloController.java
│   │   │               └── service
│   │   │                   └── HelloService.java
│   └── test
│       └── java
│           └── com
│               └── frontbackend
│                   └── springboot
│                       └── controller
│                           ├── HelloControllerMockitoTest.java
│                           ├── HelloControllerMockMvcTest.java
│                           └── HelloControllerRestTemplTest.java

Here we could distinguish the following classes:

  • Application - the main Spring Boot application class used for starting web container,
  • HelloController - Spring Rest Controller for testing purposes,
  • HelloService - Spring Service used to check how autowire works in tests,
  • HelloControllerMockitoTest - test for HelloController using Mockito,
  • HelloControllerMockMvcTest - test for HelloController using MockMvc,
  • HelloControllerRestTemplTest - test for HelloController using TestRestTemplate.

4. Create a Spring REST API for JUnit 5 Testing

Spring REST controller called HelloController will be used as our main class for testing. It uses @Autowired annotation to inject HelloService.

The HelloController has the following structure:

package com.frontbackend.springboot.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import com.frontbackend.springboot.service.HelloService;

@RestController
public class HelloController {

    private final HelloService helloService;

    @Autowired
    public HelloController(HelloService helloService) {
        this.helloService = helloService;
    }

    @GetMapping("/")
    public @ResponseBody String greeting() {
        return helloService.getWelcomeMessage();
    }
}

@GetMapping annotation marks greeting() method that from now will be used to handle all GET requests to the root path /.

5. Create sample Service

The HelloService is a simple Spring service with method getWelcomeMessage() that will be run in REST controller class:

package com.frontbackend.springboot.service;

import org.springframework.stereotype.Service;

@Service
public class HelloService {

    public String getWelcomeMessage() {
        return "Hello World!";
    }
}

The main purpose of this class is to show how we can mock classes using Mockito in JUnit tests.

6. Create @SpringBootApplication class

Main Spring Boot application class is marked with @SpringBootApplication annotation and contains a single public static void main(String[] args) method that starts web container - Tomcat by default.

package com.frontbackend.springboot;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

7. Test using MockMvc to perform REST calls

Let's start our testing adventure with a test class that uses MockMvc. In this approach, the Spring Boot application server will not be started but the code will be called exactly in the same way as if it handling an HTTP request.

The test class looks as follows:

package com.frontbackend.springboot.controller;

import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;

@SpringBootTest
@AutoConfigureMockMvc
public class HelloControllerMockMvcTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void shouldReturnDefaultMessage() throws Exception {
        this.mockMvc.perform(get("/"))
                    .andDo(print())
                    .andExpect(status().isOk())
                    .andExpect(content().string(containsString("Hello World!")));
    }
}

We used the MockMvc class and @AutoConfigureMockMvc that will configure it and inject it into the tested class. The MockMvc class is used to perform API calls, but instead of doing HTTP requests, Spring will test only the implementation that handle them in HelloController.

8. Spring boot test that makes use of TestRestTemplate to call REST API

In the next approach, we will use the @SpringBootTest annotation that is used to start the Spring application context.

package com.frontbackend.springboot.controller;

import static org.assertj.core.api.Assertions.assertThat;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HelloControllerRestTemplTest {

    @LocalServerPort
    private int port;

    private String url;

    @Autowired
    private TestRestTemplate restTemplate;

    @BeforeEach
    public void setUp() {
        url = String.format("http://localhost:%d/", port);
    }

    @Test
    public void greetingShouldReturnDefaultMessage() {
        assertThat(this.restTemplate.getForObject(url, String.class)).contains("Hello World!");
    }
}

In this approach, we can use @Autowired annotation just like in runtime applications. Spring will interpret them and do the necessary injections. In @SpringBootTest tests real Spring Boot application server is being started.

In the test we used:

  • webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT - to start the server with a random port in order to avoid any port conflicts,
  • @LocalServerPort - this annotation tells Spring to inject a random port to the specific field,
  • TestRestTemplate - RestTemplate for tests used to make a real HTTP requests.

9. Mockito unit test for HelloController class

In case we want to mock some objects while testing the HelloController class we could use the Mockito framework in a unit test. In this approach, we are simply testing class with JUnit and Mockito without making any HTTP calls. It is possible because our REST controller is an ordinary class like any other.

package com.frontbackend.springboot.controller;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import com.frontbackend.springboot.service.HelloService;

@ExtendWith(MockitoExtension.class)
public class HelloControllerMockitoTest {

    @Mock
    private HelloService helloService;

    @InjectMocks
    private HelloController helloController;

    @BeforeEach
    void setMockOutput() {
        when(helloService.getWelcomeMessage()).thenReturn("Hello Mockito Test");
    }

    @Test
    public void shouldReturnDefaultMessage() {
        String response = helloController.greeting();
        assertThat(response).isEqualTo("Hello Mockito Test");
    }
}

To start using Mockito in JUnit tests we need to annotate a class with @ExtendWith(MockitoExtension.class). In our testing class, we mock HelloService to change the response of getWelcomeMessage() method. The @InjectMocks annotation tells Mockito to inject all mock objects into the test class.

10. Conclusion

In this article, we presented several approaches to test the Spring REST controller using JUnit 5 and the Mockito library. It is up to us if we want to start the real Spring Boot server using @SpringBootTest annotation or simply run the implementation that is called on HTTP requests using MockMvc. Mockito could be also used to test the REST controller class if there is a need to mock or spy dependencies.

As usual, code used in this tutorial is available on our GitHub.

{{ message }}

{{ 'Comments are closed.' | trans }}