“This is the 29th day of my participation in the Gwen Challenge in November. See details of the event: The Last Gwen Challenge in 2021”

This article was included in the column “Spring Boot Actual Combat”.

Hello, I’m looking at the mountains.

Today we’ll 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 topped the charts for a while.

After upgrading to Junit5, in addition to adding many features of Java8, we have made many feature enhancements, structural optimizations, and broken down many different modules, which can be introduced on demand, such as:

  • JUnit Platform – Launches the test framework on the JVM
  • JUnit Jupiter – Write tests and extensions in JUnit5
  • JUnit Vintage – Provides test engines that run on JUnit3 and JUnit4

Since SpringBoot 2.2.0, Junit5 has become the default Junit version. With JUnit Vintage, migrating from JUnit 4 to JUnit 5 is extremely cheap. So this article begins directly with Junit5.

version

To avoid all sorts of weird issues with different versions, I’ll start with version:

  • JDK: JDk8 (smaller versions can be ignored)
  • SpringBoot: 2.5.2
    • inheritancespring-boot-starter-parent
    • Rely onspring-boot-starter-web
    • Rely onspring-boot-starter-test
  • JUnit: 5.7.2
  • Mockito: 3.9.0
  • Hamcrest: 2.2

Spring-boot-starter-test dependency spring-boot-starter-test dependency spring-boot-starter-test dependency spring-boot-starter-test dependency spring-boot-starter-test dependency The POM is defined as follows:


      
<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 - the 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>
Copy the code

Since spring-boot-starter-parent is inherited, 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. Spring-boot-starter – Web is a web container used to provide REST APIS. Spring-boot-starter -test provides various test frameworks. Spring-boot-maven-plugin is a plug-in that packages SpringBoot applications as executable jars.

The project structure

As a DEMO example, we implement an Echo interface that accepts request parameters and returns the processed string. By convention, we use the all-purpose Hello, World! .

Our project structure is as follows:

├ ─ ─ pom. XML └ ─ ─ the SRC ├ ─ ─ the main │ ├ ─ ─ Java │ │ └ ─ ─ cn │ │ └ ─ ─ howardliu │ │ └ ─ ─ the effective │ │ └ ─ ─ spring │ │ └ ─ ─ Springbootjunit5mockio │ │ ├ ─ ─ SpringbootJunit5MockioApplication. Java │ │ ├ ─ ─ controller │ │ │ └ ─ ─ EchoController. Java │ │ └ ─ ─ service │ │ ├ ─ ─ the EchoService. Java │ │ └ ─ ─ impl │ │ └ ─ ─ EchoServiceImpl. Java │ └ ─ ─ resources │ └ ─ ─ application. The yaml ├─ all, all, all, all, all, all, all, all, all EchoControllerMockTest. Java └ ─ ─ EchoControllerNoMockitoTest. JavaCopy the code
  • SpringbootJunit5MockioApplication: SpringBoot application startup entries
  • EchoController: defines the interface
  • EchoService: Implements the service logic interface
  • EchoServiceImpl: Interface implementation
  • EchoControllerMockTest: Implemented using the Mock proxy EchoService
  • EchoControllerNoMockitoTest: direct test interface implementation

EchoServiceImpl

Let’s look at the implementation of EchoService, which will be the core implementation of our DEMO:

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

EchoControllerNoMockitoTest

We first use Junit5+MockMvc to implement a normal call to the Controller interface as follows:

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

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

        Assertions.assertEquals("Hello, see the mountains", result); }}Copy the code

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

One thing to note here is that many tutorials online require @extendwith ({springExtension.class}) as an annotation, which is completely unnecessary. As you can see from the source code, ExtendWith has been added to the Spring Ttest annotation.

EchoControllerMockTest

In this test case, we used Mockito component to proxy echo method of EchoService, 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(a) {
        Mockito.when(echoService.echo(Mockito.any()))
                .thenReturn("Look at the mountain and say:" + System.currentTimeMillis());
    }

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

        Assertions.assertTrue(result.startsWith("Mountain")); }}Copy the code

In this example, we need to note the @extendWith (mockitoExtension.class) annotation, which is used to introduce a MockBean by intercepting the echo method to return the result of our defined response. This is done so that you don’t need to actually call the interface during multi-system or multifunctional testing.

For example, if we need to get the user’s mobile phone number and verify whether the user is logged in, we can use Mockito’s ability proxy login authentication to make the result always true.

At the end of the article to summarize

At this point, we have completed examples of SpringBoot integration with Junit5, MockMvc, and Mockito. To obtain the source code, just need to pay attention to the public account “mountain hut”, reply to “Spring”.

Many students feel that there is no need to write unit tests. They can use tools like Swagger or Postman to test interfaces well. Indeed, for simple CRUD interfaces, there is less need to write unit tests. But what about complex interfaces? There are many combinations of interface parameters, and the response results need to be verified. If you use a one-time tool, each time you test the combination parameters, it will be broken. Moreover, the combination parameters can not survive or even be inherited from multiple people, which will waste a lot of manpower.

At this point, the effect of unit testing will come into play. We only need to write the parameter combination once, put it in a file like CSV, and run it many times through the parameterized testing method of unit test to verify the correctness of the interface.

Or, when we feel that the system is smelly, we can refactor it and use the original test case to verify that the interface function remains the same.

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

Recommended reading

  • SpringBoot: elegant response to achieve results in one move
  • SpringBoot: How to handle exceptions gracefully
  • SpringBoot: Dynamically injects the ID generator through the BeanPostProcessor
  • SpringBoot: Customizes Filter to gracefully obtain request parameters and response results
  • SpringBoot: Elegant use of enumeration parameters
  • SpringBoot: Elegant use of enumeration parameters (Principles)
  • SpringBoot: Gracefully use enumeration parameters in the RequestBody
  • SpringBoot: Gracefully using enumeration parameters in the RequestBody
  • Do unit tests with JUnit5+MockMvc+Mockito
  • SpringBoot: Loads and reads resource files

Hello, I’m looking at the mountains. Swim in the code, play to enjoy life. If this article is helpful to you, please like, bookmark, follow. Welcome to follow the public account “Mountain Hut”, discover a different world.