SpringBoot practice: JUnit5+MockMvc+Mockito do unit testing well

This picture was created by NickyPe stay Pixabay Publish on

Hello, I'm looking at the mountain.

Today, let's talk about how to integrate junit5, MockMvc and Mocktio in SpringBoot. Junit5 is the most widely used testing framework in the Java stack, and Junit4 once dominated the list.

After upgrading to JUnit 5, in addition to adding many features of Java 8, many function enhancements have been made, the structure has been optimized and adjusted, and many different modules have been split, which can be introduced on demand, such as:

  • JUnit Platform - start the test framework on the JVM
  • JUnit Jupiter - write tests and extensions in JUnit 5
  • JUnit Vintage - provides a test engine that runs JUnit 3 and JUnit 4

Junit 5 has become the default Junit version since SpringBoot 2.2.0. With JUnit Vintage, the cost of migrating from Junit4 to Junit5 is very low. So this article starts directly with Junit5.

edition

Let's talk about versions first to avoid various strange problems due to version differences:

  • JDK: jdk8 (minor versions can be ignored)
  • SpringBoot: 2.5.2
    • Inherit spring boot starter parent
    • Rely on spring boot starter Web
    • Rely on spring boot starter test
  • JUnit: 5.7.2
  • Mockito: 3.9.0
  • hamcrest: 2.2

The advantage of SpringBoot is that it only needs to inherit spring boot starter parent or introduce spring boot POM dependencies, and then add spring boot starter test dependencies. POM is defined as follows:

<?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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.2</version>
    </parent>
    <groupId>cn.howardliu.effective.spring</groupId>
    <artifactId>springboot-junit5-mockito</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot-junit5-mockio</name>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Because it inherits the spring boot starter parent, the spring boot starter test we rely on does not need to write a specific version, and can directly integrate the version definition of the parent. Among them, spring boot starter web is a web container used to provide rest APIs, spring boot starter test can provide various test frameworks, and spring boot Maven plugin is a plug-in that packages spring boot applications as executable jar s.

Project structure

Because it is a DEMO example, we implement an Echo interface that can receive request parameters and return processed strings. By convention, we use the universal Hello, World!.

Our project structure is as follows:

├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── cn
    │   │       └── howardliu
    │   │           └── effective
    │   │               └── spring
    │   │                   └── springbootjunit5mockio
    │   │                       ├── SpringbootJunit5MockioApplication.java
    │   │                       ├── controller
    │   │                       │   └── EchoController.java
    │   │                       └── service
    │   │                           ├── EchoService.java
    │   │                           └── impl
    │   │                               └── EchoServiceImpl.java
    │   └── resources
    │       └── application.yaml
    └── test
        └── java
            └── cn
                └── howardliu
                    └── effective
                        └── spring
                            └── springbootjunit5mockio
                                └── controller
                                    ├── EchoControllerMockTest.java
                                    └── EchoControllerNoMockitoTest.java
  • SpringBoot JUnit 5mockioapplication: SpringBoot application startup portal
  • EchoController: interface definition
  • EchoService: implement business logic interface
  • EchoServiceImpl: interface implementation
  • EchoControllerMockTest: implement EchoService using Mock proxy
  • EchoControllerNoMockitoTest: direct test interface implementation

EchoServiceImpl

Let's take a look at the implementation of EchoService, which will be the core implementation of DEMO:

@Service
public class EchoServiceImpl implements EchoService {
    @Override
    public String echo(String foo) {
        return "Hello, " + foo;
    }
}

EchoControllerNoMockitoTest

First, we use Junit5+MockMvc to realize the common call of Controller interface. The code is as follows:

@SpringBootTest(classes = SpringbootJunit5MockioApplication.class)
@AutoConfigureMockMvc
class EchoControllerNoMockitoTest {
    @Autowired
    private MockMvc mockMvc;

    @Test
    void echo() throws Exception {
        final String result = mockMvc.perform(
                MockMvcRequestBuilders.get("/echo/")
                        .param("name", "Look at the mountain")
        )
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print())
                .andReturn()
                .getResponse()
                .getContentAsString(StandardCharsets.UTF_8);

        Assertions.assertEquals("Hello, Look at the mountain", result);
    }
}

We define this as a test case of a SpringBoot application through the SpringBootTest annotation, and then start the test container through AutoConfigureMockMvc. In this way, you can directly inject the MockMvc instance to test the Controller interface.

It should be noted here that many online tutorials will let you write such an annotation as @ ExtendWith({SpringExtension.class}), which is not necessary at all. From the source code, we can know that the spring boottest annotation has added ExtendWith.

EchoControllerMockTest

In this test case, we proxy the echo method of EchoService through the Mockito component. The code is as follows:

@SpringBootTest(classes = SpringbootJunit5MockioApplication.class)
@ExtendWith(MockitoExtension.class)
@AutoConfigureMockMvc
class EchoControllerMockTest {
    @Autowired
    private MockMvc mockMvc;
    @MockBean
    private EchoService echoService;

    @BeforeEach
    void setUp() {
        Mockito.when(echoService.echo(Mockito.any()))
                .thenReturn("Look at the mountain and say:" + System.currentTimeMillis());
    }

    @Test
    void echo() throws Exception {
        final String result = mockMvc.perform(
                MockMvcRequestBuilders.get("/echo/")
                        .param("name", "Look at the cottage on the mountain")
        )
                .andExpect(MockMvcResultMatchers.status().isOk())
                .andDo(MockMvcResultHandlers.print())
                .andReturn()
                .getResponse()
                .getContentAsString(StandardCharsets.UTF_8);

        Assertions.assertTrue(result.startsWith("Look at the mountain"));
    }
}

In this example, we need to pay attention to the @ ExtendWith(MockitoExtension.class) annotation, which is used to introduce MockBean. We intercept the echo method to return the response results we have defined. This method is to avoid calling the interface when testing multiple systems or functions.

For example, we need to obtain the user's mobile phone number. Usually, the user's login will be verified in the interface. Then we can use Mockito's ability to proxy login verification to make the result always true.

Summary at the end of the paper

So far, we have completed the example of spring boot integrating Junit5, MockMvc and Mockito. To get the source code, you only need to pay attention to the official account of the "hill view cabin" and reply to spring.

Many students feel that there is no need to write unit tests. They can test interfaces well by directly using tools such as Swagger or Postman. Indeed, it is not necessary to write unit tests for simple CRUD interfaces. But what if it's a complex interface? There are many combinations of interface parameters, and the response results also need to be verified. If a one-time tool is used, the combined parameters will crash every time they are tested, and the combined parameters cannot be preserved or even inherited among multiple people, which will waste a lot of manpower.

At this point, the effect of unit testing will appear. We only need to write a parameter combination once and put it in files such as csv. Through the parametric test method of unit test, we can run it many times to verify the correctness of the interface.

Or, when we feel that the system has been smelly, we can directly use the original test cases to verify that the interface function remains unchanged after its reconstruction.

To sum up, although it is troublesome to write test cases, they are infinitely useful.

Recommended reading

Hello, I am looking at the mountain, official account: looking at the mountain cottage, the 10 year old ape, the contributor of the open source. Swim in the code world and enjoy life.

Personal homepage: https://www.howardliu.cn
Personal blog: SpringBoot practice: JUnit5+MockMvc+Mockito do unit testing well
CSDN home page: https://kanshan.blog.csdn.net/
CSDN blog: SpringBoot practice: JUnit5+MockMvc+Mockito do unit testing well

Keywords: Java Spring Boot unit testing

Added by bfinucan on Sun, 12 Sep 2021 03:08:51 +0300