在面向对象的程序设计中,模拟对象(英语:mock object)是以可控的方式模拟真实对象行为的假对象。在编程过程中,通常通过模拟一些输入数据,来验证程序是否达到预期结果。
使用Mock一般分三个步骤:
1、模拟测试类所需的外部依赖;
2、执行测试代码;
3、判断执行结果是否达到预期。
基于RESTFul风格的SpringMVC单元测试,可以测试完整的SpringMVC流程,即从URL请求到控制处理器,带到视图渲染都可以测试。MockMvc是由spring-test包提供,实现了对Http请求的模拟访问。
MockMvcBuilder接口,提供一个唯一的build方法,用来构造MockMvc。
主要有两个实现:StandaloneMockMvcBuilder和DefaultMockMvcBuilder,分别对应两种测试方式,即独立安装和集成Web环境测试(并不会集成真正的web环境,而是通过相应的Mock API进行模拟测试,无须启动服务器)。
MockMvcBuilders提供了对应的创建方法standaloneSetup方法和webAppContextSetup方法,在使用时直接调用即可。
第一步:引入jar包。创建SpringBoot项目中默认引入的spring-boot-starter-test间接引入了spring-test,因此无需再额外引入jar包。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
第二步:创建HelloWorldController类,并提供test方法作为待测试的业务接口。
package com.text.mockmvc.controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class Controller {
@RequestMapping("/test")
@GetMapping
public String test(){
return "hello mock mvc";
}
@RequestMapping("/delete")
@DeleteMapping
public void delete(){
System.out.println("删除成功!");
}
}
第三步:编写测试类 相关解释在代码中进行了详细注释
package com.text.mockmvc;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
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.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
@RunWith(SpringRunner.class)
@WebAppConfiguration
@SpringBootTest(classes = SpringTestMockmvcApplication.class)
public class SpringTestMockmvcApplicationTests {
@Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
@Before
public void setUp() throws Exception {
this.mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
}
@Test
public void contextLoads() throws Exception {
MvcResult mvcResult = mockMvc.perform(
MockMvcRequestBuilders.get("/test") // 构建get方式来调用接口
// MockMvcRequestBuilders.get("/delete") // 构建get方式来调用接口
.contentType(MediaType.APPLICATION_JSON_UTF8) // 设置请求参数的类型
.param("hello", "world")// 构建请求参数
).andExpect(MockMvcResultMatchers.status().isOk())//添加ResultMatcher验证规则,验证控制器执行完成后结果是否正确
.andReturn();//最后返回相应的MvcResult;然后进行自定义验证/进行下一步的异步处理。
System.out.println("执行后的mvcResult:------" + mvcResult.getResponse().getContentAsString());
}
}
执行结果如下图所示:
实际项目中使用如下,由于项目是整合JWT验证,这里访问还需要进行权限的验证,所以我在.header(“Authorization”, authorization),这里进行了配置。
@Slf4j
@RestController
@RequestMapping("/api/bg")
@Api(value = "BG绩效仪表盘", tags = {"BG绩效仪表盘"})
public class BzBgPanelController {
@Autowired
IBzBgPanelService bzBgPanelService;
@Autowired
IBzCustomerPanelService bzCustomerPanelService;
@ApiOperation("查询年度指标数据")
@AutoLog(value = "查询年度指标数据")
@RequestMapping(value = "/panel", method = RequestMethod.GET)
public Result getBgYearPanelIndex(@RequestParam(name = "business_type") String businessType,@RequestParam(name = "second_product_line") String secondProductLine,@RequestParam(name = "year") String year)
{
Result<LinkedHashMap<String,Object>> result = new Result<>();
LinkedHashMap<String, Object> entitiesBack = bzBgPanelService.getBgPanelIndex(businessType,secondProductLine,year);
if(entitiesBack==null)
return Result.error(401,"未查询到数据");
result.setResult(entitiesBack);
result.success("OK");
return result;
}
}
@RunWith(SpringRunner.class)
@SpringBootTest
public class BzBgPanelControllerTest {
@Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
@Before
public void setupMockMvc() {
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
org.apache.shiro.mgt.SecurityManager securityManager = (org.apache.shiro.mgt.SecurityManager) webApplicationContext.getBean("securityManager");
SecurityUtils.setSecurityManager(securityManager);
}
@Test
public void getBgYearPanelIndex() throws Exception {
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/api/bg/panel")
.header("Authorization", "************************")
.param("business_type", "A")
.param("second_product_line", "A01")
.param("year", "2021")
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(print())
.andReturn();
//自定义断言
/*int status=mvcResult.getResponse().getStatus(); //得到返回代码
String content=mvcResult.getResponse().getContentAsString(); //得到返回结果
Assert.assertEquals(200,status); //断言,判断返回代码是否正确
Assert.assertEquals("hello lvgang",content); //断言,判断返回的值是否正确*/
}
}
前面配置不变,只需要更改测试类即可。
package com.text.mockmvc;
import static org.junit.Assert.*;
import org.junit.Test;
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 com.text.mockmvc.controller.Controller;
@RunWith(SpringRunner.class)
//注意这里一定要是程序主类名SpringTestMockmvcApplication
@SpringBootTest(classes = SpringTestMockmvcApplication.class)
public class SpringJunitTest {
@Autowired
private Controller controller;
@Test
public void testTest() {
//执行控制层的test方法
System.out.println(controller.test());
}
@Test
public void testDelete() {
//执行控制层的delete方法
controller.delete();
}
}
执行结果如下图所示:
注意:如果需要测试包含Shiro Subject的控制层方法,请参考下篇博客
MockMVC登录后测试SpringBoot项目包含Shiro Subject的控制层方法
参考资料
在SpringBoot中使用MockMvc进行单位测试
MockMvc进行单元测试
SpringBoot基础之MockMvc单元测试
mockMvc使用教程