从源码上学习 MockMvc 的使用

812 阅读2分钟

步骤:

  1. 新建一个测试类
  2. 给测试类添加类注解 @RunWith(SpringRunner.class) @SpringBootTest @WebAppConfiguration
  3. 注入私有属性 @Autowire private WebApplicationContext context;
  4. 定义一个私有的共用属性 private MockMvc mock;
  5. 编写一个Junit测试的前置方法(在测试方法前执行,做准备工作,@Before
@Before 
public void before() throws Exception { 
mock = MockMvcBuilders.webAppContextSetup(context).build(); }
  1. controller 中的方法进行测试(示例测试 UserController_findAll)
@Test 
public void testFindAll(){ 
MvcResult result = mock.perform( MockMvcRequestBuilders.get('/user'))
    .andDo(MockMvcResultHandlers.print())
    .andReturn();
}

一般使用形式

ResultActions action = mock.perform(RequestBuilder);
action.andExpect(ResultMatcher)
    .andDo(ResultHandler)
    .andReturn();

interface RequestBuilder

构建 request 请求

需要使用的方法,可以在其实现类 MockHttpServletRequestBuilder 中查阅到

摘抄部分源码

    /**
     * Set the character encoding of the request.
     * @param encoding the character encoding
     */
    public MockHttpServletRequestBuilder characterEncoding(String encoding) {
        this.characterEncoding = encoding;
        return this;
    }

    /**
     * Set the request body.
     * @param content the body content
     */
    public MockHttpServletRequestBuilder content(byte[] content) {
        this.content = content;
        return this;
    }

    /**
     * Set the request body as a UTF-8 String.
     * @param content the body content
     */
    public MockHttpServletRequestBuilder content(String content) {
        this.content = content.getBytes(StandardCharsets.UTF_8);
        return this;
    }

    /**
     * Set the 'Content-Type' header of the request.
     * @param contentType the content type
     */
    public MockHttpServletRequestBuilder contentType(MediaType contentType) {
        Assert.notNull(contentType, "'contentType' must not be null");
        this.contentType = contentType.toString();
        return this;
    }

常用方法

contentType(MediaType);
content(String);
param(String, String...);
requestAttr(String, Object);
accept(MediaType);
headers(HttpHeaders);

interfact ResultMatcher

函数式接口,对结果,进行条件匹配

org.springframework.test.web.servlet.result 包下的 *ResultMatchers中,定义了绝大部分的返回 ResultMatcher 的匿名实现类的方法

# 所有的类
ContentResultMatchers.class
CookieResultMatchers.class
FlashAttributeResultMatchers.class
HandlerResultMatchers.class
HeaderResultMatchers.class
JsonPathResultMatchers.class
MockMvcResultHandlers.class
MockMvcResultMatchers.class
ModelResultMatchers.class
PrintingResultHandler.class
RequestResultMatchers.class
StatusResultMatchers.class
StatusResultMatchersExtensionsKt.class
ViewResultMatchers.class
XpathResultMatchers.class

.andExpect(status().isOk()) 为案例追踪源码

// StatusResultMatchers.isOk()
/**
  * Assert the response status code is {@code HttpStatus.OK} (200).
  */
public ResultMatcher isOk() {
    return matcher(HttpStatus.OK);
}
/**
  * Match the expected response status to that of the HttpServletResponse.
  */
private ResultMatcher matcher(final HttpStatus status) {
    return result -> assertEquals("Status", status.value(), result.getResponse().getStatus());
}

在 .andExpect(ResultMatcher)中的参数,其实,通过 MockMvcResultMatchers中的静态方法,去生成对应的 Matcher

    // 
    /**
     * Access to response status assertions.
     */
    public static StatusResultMatchers status() {
        return new StatusResultMatchers();
    }

interface ResultHandler

函数式接口,对获得的结果,进行处理

常见的处理为输出到日志,或输出到控制台,或输出到指定的输出流

推荐使用 MockMvcResultHandlers 中的方法,从源码可得知,MockMvcResultHandlers 中定义了私有的静态内部类对 PrintingResultHandlerResultHandler 进行了扩展

// 可以使用的 MockMvcResultHandlers 中的静态方法
log();
print();
print(OutputStream);
print(Writer);

完整的测试示例

@RunWith(SpringRunner.class)
@SpringBootTest
@WebAppConfiguration
public class StationTest {
    @Autowired
    private WebApplicationContext context;
    private MockMvc mock;
    private String baseUrl = "/station";
    private List<Station> list;
    private MvcResult result;
    private String id;

    @Before
    public void before() throws Exception {
        mock = MockMvcBuilders.webAppContextSetup(context).build();
        list = JsonUtil.toList(
                mock.perform(get(baseUrl)).andReturn().getResponse().getContentAsString(),
                Station.class);
        System.err.println("\n==============================================================================\n");
        id = list.get(0).getId();
    }

    @After
    public void after() throws Exception {
        SimpleLogUtil.error(result.getResponse().getContentAsString());
    }

    @Test
    public void testFindAll() throws Exception {
        result = mock.perform(MockMvcRequestBuilders.get(baseUrl))
                .andExpect(status().isOk())
                .andDo(MockMvcResultHandlers.print())
                .andReturn();
        list = JsonUtil.toList(result.getResponse().getContentAsString(), Station.class);
        id = list.get(0).getId();
        SimpleLogUtil.error(list);
    }

    @Test
    public void testSave() throws Exception {
        Station station = new Station(RCSUtil.CreateID(), "station-1", 0, 1, "2", "1", 20, "10");
        String json = JsonUtil.toJson(station);
        result = mock
                .perform(MockMvcRequestBuilders.post(baseUrl)
                        .contentType(MediaType.APPLICATION_JSON_UTF8)
                        .content(json))
                .andExpect(status().isOk())
                .andDo(MockMvcResultHandlers.print())
                .andReturn();
    }

    @Test
    public void testGet() throws Exception {
        result = mock.perform(MockMvcRequestBuilders.get(baseUrl + "/{id}", list.get(0).getId()))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andDo(MockMvcResultHandlers.print())
                .andReturn();
    }

    @Test
    public void testUpdate() throws Exception {
        Station station = list.get(0);
        station.setName("st-123");
        station.setWalkType("0");
        result = mock
                .perform(MockMvcRequestBuilders.post(baseUrl + "/{id}", id)
                        .contentType(MediaType.APPLICATION_JSON_UTF8)
                        .content(JsonUtil.toJson(station)))
                .andExpect(status().isOk())
                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andDo(MockMvcResultHandlers.print())
                .andReturn();
    }

    @Test
    public void testDelete() throws Exception {
        result = mock
                .perform(MockMvcRequestBuilders.delete(baseUrl + "/{id}", id))
                .andExpect(status().isOk())
                .andExpect(content().string("true"))
                .andDo(MockMvcResultHandlers.print())
                .andReturn();
    }

}

@RestController
@RequestMapping("/station")
public class StationController {
    @GetMapping("")
    public List<Station> findAll() {
        return service.findAll();
    }

    @PostMapping("")
    public Station save(@RequestBody Station station) {
        return service.save(station);
    }

    @GetMapping("/{id}")
    public Station get(@PathVariable String id) {
        return service.getById(id);
    }

    @PostMapping("/{id}")
    public Station update(@RequestBody Station station, @PathVariable String id) {
        station.setId(id);
        return service.save(station);
    }

    @DeleteMapping("/{id}")
    public boolean delete(@PathVariable String id) {
        return service.delete(id);
    }

    @Autowired
    private StationService service;
}