1、简介

使用模拟对象,可以模拟复杂的、真实的对象行为。如果在单元测试中无法使用真实对象,可采用模拟对象进行替代。

MockMvc是由spring-test包提供,实现了对Http请求的模拟,能够直接使用网络的形式,转换到Controller的调用,使得测试速度快、不依赖网络环境。同时提供了一套验证的工具,结果的验证十分方便。

接口MockMvcBuilder,提供一个唯一的build方法,用来构造MockMvc。主要有两个实现:StandaloneMockMvcBuilderDefaultMockMvcBuilder,分别对应两种测试方式,即独立安装和集成Web环境测试(并不会集成真正的web环境,而是通过相应的Mock API进行模拟测试,无须启动服务器)。MockMvcBuilders提供了对应的创建方法standaloneSetup方法和webAppContextSetup方法,在使用时直接调用即可。

2、为什么使用Mock对象

在以下情况可以采用模拟对象来替代真实对象:

  • 真实对象的行为是不确定的(例如,当前的时间或温度);
  • 真实对象很难搭建起来;
  • 真实对象的行为很难触发(例如,网络错误);
  • 真实对象速度很慢(例如,一个完整的数据库,在测试之前可能需要初始化);
  • 真实的对象是用户界面,或包括用户界面在内;
  • 真实的对象使用了回调机制;
  • 真实对象可能还不存在;
  • 真实对象可能包含不能用作测试(而不是为实际工作)的信息和方法。

使用Mockito一般分三个步骤:1、模拟测试类所需的外部依赖;2、执行测试代码;3、判断执行结果是否达到预期;

3、依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

4、测试

4.1、初始化

import demo.springboot.test.controller.UserController;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringRunner.class)
@SpringBootTest
//测试环境使用,用来表示测试环境使用的ApplicationContext将是WebApplicationContext类型的
@WebAppConfiguration
public class UserControllerMyTest {

	private MockMvc mockMvc;

	@Autowired
	private WebApplicationContext webApplicationContext;

	@Before
	public void create(){
		//实例化方式一
		//mockMvc = MockMvcBuilders.standaloneSetup(new UserController()).build();
		//实例化方式二
		mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
	}
}

4.2、测试方式

  1. mockMvc.perform ====> 执行一个请求。
  2. MockMvcRequestBuilders.get(“XXX”) ====> 构造一个请求。
  3. ResultActions.param ====> 添加请求传值
  4. contentType(MediaType.APPLICATION_JSON_UTF8_VALUE) ====> 设置数据格式:指定服务端能够接收的内容类型
  5. ResultActions.accept(MediaType.TEXT_HTML_VALUE)) ====> 设置返回类型:指定客户端能够接收的内容类型
  6. ResultActions.andExpect ====> 添加执行完成后的断言。
  7. ResultActions.andDo ====> 添加一个结果处理器,表示要对结果做点什么事情比如此处使用MockMvcResultHandlers.print()输出整个响应结果信息。
  8. ResultActions.andReturn ====> 表示执行完成后返回相应的结果。

4.3、测试代码示例

4.3.1、测试GET请求

@Test
public void getTest() throws Exception {
    //路径参数测试1
    mockMvc.perform(MockMvcRequestBuilders
    .get("/user/{userName}","tester")
    .contentType(MediaType.APPLICATION_JSON_VALUE))
    .andExpect(MockMvcResultMatchers.status().isOk())
    .andDo(MockMvcResultHandlers.print());

    //路径参数测试二
    //mockMvc.perform(MockMvcRequestBuilders
    //		.get("/user/{userName}/{text}","tester","哈哈")
    //		.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
    //		.accept(MediaType.APPLICATION_JSON_UTF8_VALUE))
    //		.andExpect(MockMvcResultMatchers.status().isOk())
    //		.andDo(MockMvcResultHandlers.print());

    //表单参数测试一
    //mockMvc.perform(MockMvcRequestBuilders
    //		.get("/user")
    //		.param("userName","小红")
    //		.param("text","你好")
    //		.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
    //		.accept(MediaType.APPLICATION_JSON_UTF8_VALUE))
    //		.andExpect(MockMvcResultMatchers.status().isOk())
    //		.andDo(MockMvcResultHandlers.print());

    //表单参数测试二
    MvcResult s = mockMvc.perform(MockMvcRequestBuilders
    .get("/user?userName=小红&text=你好")
    .contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
    .accept(MediaType.APPLICATION_JSON_UTF8_VALUE))
    .andExpect(MockMvcResultMatchers.status().isOk())
    .andExpect(MockMvcResultMatchers.content().string("你好"))
    .andDo(MockMvcResultHandlers.print())
    .andReturn();

    System.out.println(s.getResponse().getContentAsString());

    //路径参数使用 .get("/user/{参数1}/{参数2}",参数1,参数2)
    //表单参数使用 .param("userName","小红")
}
@Test
public void getSequenceTest() throws Exception {
    mockMvc.perform(MockMvcRequestBuilders
                    .get("/user/getSequence/{userName}","tester")
                    .contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
                    .accept(MediaType.APPLICATION_JSON_UTF8_VALUE))
        .andExpect(MockMvcResultMatchers.status().isOk())
        //对对象的参数进行校验
        .andExpect(MockMvcResultMatchers.jsonPath("$.username").value("tester"))
        .andDo(MockMvcResultHandlers.print());
}

4.3.2、测试POST请求

@Test
public void postTest() throws Exception {
    User user = new User();
    user.setUsername("小红");
    user.setPasswd("123456");
    System.out.println(new Date());
    user.setCreateTime(new Date());
    user.setStatus("2");
    ObjectMapper objectMapper = new ObjectMapper();
    //需要将对象转换成JSON字符串
    String userStr = objectMapper.writeValueAsString(user);

    mockMvc.perform(MockMvcRequestBuilders
        .post("/user/save")
        .content(userStr)
        .contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
        .accept(MediaType.APPLICATION_JSON_UTF8_VALUE))
        .andExpect(MockMvcResultMatchers.status().isOk())
        .andDo(MockMvcResultHandlers.print());
}

4.3.3、带有请求头/获取请求结果/中文乱码解决

@Test
public void test() throws Exception {
    //测试获取Token
    User user = new User();
    user.setUserID("admin");
    user.setUserName("admin");
    user.setPassWord("admin");
    //将USER转换为JSON字符串
    ObjectMapper objectMapper = new ObjectMapper();
    //需要将对象转换成JSON字符串
    String userStr = objectMapper.writeValueAsString(user);
    ResultActions resultActions = 
        mockMvc.perform(MockMvcRequestBuilders
                        .post("/getToken")
                        .content(userStr)
                        .contentType(MediaType.APPLICATION_JSON_VALUE)
                        .accept(MediaType.APPLICATION_JSON_VALUE))
        .andExpect(MockMvcResultMatchers.status().isOk());
    // 解决中文乱码问题
    resultActions.andReturn().getResponse().setCharacterEncoding("UTF-8");
    resultActions.andDo(MockMvcResultHandlers.print());

    // 测试使用Token发送请求
    Map<String, Object> restResponse = 
        objectMapper.readValue(resultActions.andReturn().getResponse().getContentAsString()
                        , new TypeReference<Map<String, Object>>() {});
    String token = restResponse.get("data").toString();

    ResultActions testTokenActions = 
        mockMvc.perform(MockMvcRequestBuilders
                        .get("/testToken")
                        // 设置请求头
                        .header("token", token)
                        .contentType(MediaType.APPLICATION_JSON_VALUE)
                        .accept(MediaType.APPLICATION_JSON_VALUE))
        .andExpect(MockMvcResultMatchers.status().isOk());
    //解决中文乱码问题
    testTokenActions.andReturn().getResponse().setCharacterEncoding("UTF-8");
    testTokenActions.andDo(MockMvcResultHandlers.print());
}