Spring Boot 2 + JUnit 5 + Mockito
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.
We are going to use the below three approaches in this article.
- Test using
MockMvc
to perform REST calls. - Spring boot test that makes use of
TestRestTemplate
to call REST API. - Mockito unit test for
HelloController
class.
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 dependency into our final pom.xml
file.
The JUnit 5 is composed of two components, such as:
- 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.demo.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 dependencies:
spring-boot-starter-web
- web container is used for creating Spring REST API.spring-boot-starter-test
- main dependencies for unit and integration testing.
The spring-boot-maven-plugin
is used to create an executable jar with the Spring Boot application.
Project structure
The testing project has the following structure:
├── pom.xml
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── demo
│ │ │ └── springboot
│ │ │ ├── Application.java
│ │ │ ├── controller
│ │ │ │ └── HelloController.java
│ │ │ └── service
│ │ │ └── HelloService.java
│ └── test
│ └── java
│ └── com
│ └── demo
│ └── springboot
│ └── controller
│ ├── HelloControllerMockitoTest.java
│ ├── HelloControllerMockMvcTest.java
│ └── HelloControllerRestTemplTest.java
We use 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 forHelloController
using Mockito,HelloControllerMockMvcTest
- test forHelloController
using MockMvc,HelloControllerRestTemplTest
- test forHelloController
using TestRestTemplate.
Spring REST API for JUnit 5 Testing
HelloController
Spring REST controller called HelloController
will be used as our main class for testing. It uses @Autowired
annotation to inject HelloService
.
@GetMapping
annotation marks greeting()
method that from now will be used to handle all GET
requests to the root path /
.
package com.demo.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.demo.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();
}
}
Hello Service
The HelloService
is a simple Spring service with method getWelcomeMessage()
that will be run in REST controller class.The main purpose of this class is to show how we can mock classes using Mockito in JUnit tests.
package com.demo.springboot.service;
import org.springframework.stereotype.Service;
@Service
public class HelloService {
public String getWelcomeMessage() {
return "Hello World!";
}
}
Main @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.demo.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);
}
}
Approach 1: 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.demo.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
.
Approach 2: 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.demo.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.
Approach 3: 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.demo.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.demo.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.
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.