使用MockMVC进行Controller单元测试

2020-12-03 15:00:24 浏览数 (1)

引入

由于MockMVC是Spring框架自带的测试组件,因此只要在项目中引入spring-boot-starter-test这个测试套件就可以使用Spring-test库中的MockMVC了。 例如Maven项目的pom.xml中添加如下的依赖

代码语言:javascript复制
<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>

以下将介绍如何使用MockMVC Mockito JUnit5 JsonUnit进行测试

待测Controller接口

代码语言:javascript复制
package com.testlink4j.controller;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.testlink4j.domain.Keywords;
import com.testlink4j.service.KeywordsService;

/**
 * Keywords RestController
 *
 * @author Antony
 * 
 */
@RestController
public class KeywordsRestController {

    @Autowired
    private KeywordsService keywordsService;
    @RequestMapping(value = "/api/keywords", method = RequestMethod.GET)
    public Keywords findKeywordById(@RequestParam(value = "id", required = true) Integer id) {
        return keywordsService.findKeywordById(id);
    }
    @PostMapping(value = "/api/keywords")
    public Integer createKeywords(@RequestBody Keywords keywords) {
        return keywordsService.createKeywords(keywords);
    }
    
}

KeywordsRestController 包含了两个Keywords相关的接口,提供查询和创建的功能。 接下来,将以查询接口为例,介绍如何对该接口进行单元测试。

代码语言:javascript复制
package com.testlink4j.controller;

import com.testlink4j.service.KeywordsService;
import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import com.alibaba.fastjson.JSON;
import com.testlink4j.domain.Keywords;
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.status;
import java.io.UnsupportedEncodingException;


public class KeywordsControllerTest {

     protected MockMvc mockMvc;

        @Mock
        private KeywordsService keywordsServic;

        @InjectMocks
        KeywordsRestController keywordsRestController;
        @BeforeEach()
        public void setup() {
            MockitoAnnotations.initMocks(this);
            mockMvc = MockMvcBuilders.standaloneSetup(keywordsRestController).build();  //初始化MockMvc对象

        }

  @Test
  public void CreateKeywordsSuccessfullyTest() throws UnsupportedEncodingException, Exception {
      Keywords keywords=Keywords.builder().id(666).keyword("tester").testproject_id(333).notes("tester").build();
      Mockito.when(keywordsServic.findKeywordById(1)).thenReturn(keywords);
            String responseString = mockMvc.perform(
                    get("/api/keywords?id=1")    //请求的url,请求的方法是Post
                            .contentType(MediaType.APPLICATION_JSON)  //数据的格式
            ).andExpect(status().isOk())    //返回的状态是200
                    .andDo(print())         //打印出请求和相应的内容
                    .andReturn().getResponse().getContentAsString();   //将相应的数据转换为字符串
            assertThatJson(responseString).isEqualTo(keywords);
  }
}

findKeywordById接口通过调用KeywordsService来实现具体的查询功能。首先,和普通的基于Mockito单元测试一样,通过@Mock注解来对这个Service进行mock,并通过@InjectMocks注解实现注入。

接下来,通过

代码语言:javascript复制
mockMvc = MockMvcBuilders.standaloneSetup(keywordsRestController).build()

将keywordsRestController加载进Spring容器。 在测试执行阶段,通过对URI的访问,查询id=1的keyword。 从测试结果来看,发生了如下的一系列过程 1)Spring容器收到访问请求,并由DispatcherServlet 根据@RequestMapping将请求转发给对应的controller的接口。 2)接口收到请求,通过解析@RequestParam获取入参,并调用对应的方法执行(调用service的测试桩来返回mock结果) 3)返回接口调用结果,即HttpServletResponse 4)对response的状态进行断言(200),并打印请求和响应 5)对响应结果进行断言(json)

来看一下用例执行过程中,通过print()方法打印的请求和响应

代码语言:javascript复制
MockHttpServletRequest:
      HTTP Method = GET
      Request URI = /api/keywords
       Parameters = {id=[1]}
          Headers = [Content-Type:"application/json"]
             Body = <no character encoding set>
    Session Attrs = {}

Handler:
             Type = com.testlink4j.controller.KeywordsRestController
           Method = com.testlink4j.controller.KeywordsRestController#findKeywordById(Integer)
//中间部分省略
MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"application/json"]
     Content type = application/json
             Body = {"id":666,"keyword":"tester","testproject_id":333,"notes":"tester"}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

另外,看一下整个容器的启动耗时

19:29:57.689 [main] INFO org.springframework.test.web.servlet.TestDispatcherServlet - Completed initialization in 6 ms

可以看到,由于整个测试过程中只将被测的controller注入到了Spring容器中,容器的启动过程是非常快速的。

与直接通过类和方法调用的单元测试方式相比,通过使用MockMvc,有如下的不同 1)通过URI进行接口调用,也就是额外测试了DispatcherServlet 和@RequestMapping

2) 对@RequestParam进行了测试(感兴趣的读者可以尝试调用接口时不提供id=1的入参)

3)对接口返回进行了断言

4)对接口返回对象的反序列化进行了断言

下一篇将介绍如何使用MockMvc进行集成测试,并分析MockMVC的具体组成和使用方式。

0 人点赞