概述
在传统的Springboot项目中,当你按照接口文档写完API后,会手写测试用例对接口进行测试,包括接口是否是连通的,边界值等, 这是我们在完成任务时对代码质量的保证。而在线上部署时,要求我们在部署前去做接口的最后一次测试,以便于在maven/gradle build项目时,能够安全通过最后一道关卡。
顺带一提,我们自己使用的是CI/CD 去构建项目的。
步骤
请确定你自己的版本 , 这很重要,不同版本的实现细节有区别
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.4.2</version>
在此版本下 (不说版本的都是耍流氓~)
//标准Springboot测试需要加的注解,classes指定一个Springboot启动类
@SpringBootTest(classes = UserApplication.class)
//自动注入MockMvc
@AutoConfigureMockMvc
class UserControllerTest{
@Autowired
private MockMvc mockMvc;
...
}
这里MockMvc 也有别的注入方式, 可以通过下列,
@Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
// ++++
@BeforeEach
public void setUp() throws Exception{
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();//建议使用这种
}
注: 有些可能会使用@Bofore 不生效, 还是去看junit的版本,官网有说:某些版本以后要使用@BeforeEach替换@Before
在上述完成以后,就能正常去写我们的测试用例了。
@Test
void test() throws Exception {
MvcResult mvcResult = mockMvc.perform(
MockMvcRequestBuilders.get("/api/v1/usr/locked")
.contentType(MediaType.APPLICATION_JSON)
.accept(MediaType.parseMediaType("application/json;charset=UTF-8"))
.param("username" , "xxx"))
//期望结果状态为200
.andExpect(MockMvcResultMatchers.status().isOk())
//需要打印到控制台
.andDo(MockMvcResultHandlers.print())
//返回结果
.andReturn();
}
使用已经注入好的mockMvc发起调用,这里可以使用MockMvcRequestBuilders.get/post/delete/put rest风格的方式去调用,因为默认调用的是本身,所以只需要些URI,和我们使用Controller是一样的,可以参照@RequestMapping去写。这里传递参数有下列几种方式
//1 post
UserVo userVo = new UserVo();
userVo.setUsername("xx");
userVo.setPassword("xx");
JSONObject jsonObject = new JSONObject(userVo);
String jsonStr = jsonObject.toString();
MockMvcRequestBuilders.post("/api/v1/usr/locked")
.content(jsonStr)
//2 get(1)
MockMvcRequestBuilders.get("/api/v1/usr/locked")
.param("username" , "xxx"))
//3 get(2) 直接拼接uri传递参数
执行结果:
MockHttpServletRequest:
HTTP Method = POST
Request URI = /api/v1/usr/password
Parameters = {}
Headers = [Content-Type:"application/json;charset=UTF-8", Accept:"application/json;charset=UTF-8", Content-Length:"41"]
Body = {"password":"xxx","username":"xx"}
Session Attrs = {}
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Content-Type:"application/json;charset=UTF-8"]
Content type = application/json;charset=UTF-8
Body = {"status":0,"message":"success","data":xxxxxxxxxxxxxxxxx}
Forwarded URL = null
Redirected URL = null
Cookies = []
会看到一系列的结果,可以使用Assert断言去判断想要的结果;
其实到这里,我们只需要把每个测试用例的入参、uri 等按照请求规则填写好,那么在正常build过程,就可以走过所有的测试方法,实现maven/gradle build 在构建时,提前对接口进行测试。
问题描述
但是由于存在测试修改数据的风险,比如增删改的操作,某些情况是不能使用这样的方法测试的。比如你要删除某个用户,你的测试用例也只能够用一次,遇到这样的情况,如果直接修改库,看起来会很笨拙,测试也增大了开发的负担。
我们可以用到 @MockBean 注解。这个注解会生成相应类的代理类,代理后在执行测试时,是不会走实际的被代理类的方法,而是走代理类;这样在核心的业务这里就不会产生脏数据。
有如下几个维度去使用@MockBean:
- Controller
@Autowired
private MockMvc mockMvc;
//在Controller层标记MockBean
@MockBean
private UserController userController;
只会测试到接口是否正常,body始终都是空的,因为并没有产生对真实Controller的调用,所以没有返回结果
MockHttpServletResponse:
Status = 200
Error message = null
Headers = []
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
- Service
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
其他结果不变,会输出(如果有真实结果也无法调用到具体的Service方法,Service断点没有拦截到)
Body = {“status”:0,“message”:“success”,“data”:null}
没有调用到Service,只是简单测试了接口的连通性,当然如果你的测试用例有问题,照样是无法通过的
mvn install
[INFO] xxx-user-api … FAILURE [ 8.245 s]
- Mapper
@MockBean
private UserMapper userMapper;
如果是增删改的操作,使用mapper层会调用到Service层的业务逻辑, 但是userMapper是调用不到的,这里使用MockBean生成的代理去替换了userMapper
如果其他方面没有影响,使用到数据库/其他可能修改到的数据结构 前的一层测试是最完美的。
eg: redisTemplate
结论
至此 ,Springboot的接口测试可以分为两部分
- 查询操作,可以构建测试用例,对接口进行测试,可以通过andExpect的方式添加期望结果,进行接口的简单测试或者是加入断言对结果进行判断;
- 影响最终结果的测试,那么在可能会影响数据结果前使用@MockBean注入,那么既不会对结果产生影响,又能测试对应接口该有的功能。