Spring Boot的单元测试
- 一、前言
- 二、Spring Boot单元测试程序模板
- 三、测试Service
- 四、测试Controller
- 五、模拟Controller请求
- 六、比较Controller请求返回的结果
- 七、[实例](https://blog.csdn.net/qq_43753724/article/details/115559734)
一、前言
测试是系统开发中非常重要的工作,单元测试是在帮助开发人员编写高品质的程序、提升代码质量方面发挥了极大的作用。 Spring Boot未测试提供了一个名为spring-boot-starter-test的Starter。使用Spring Initializr创建Spring Boot应用时,将自动添加spring-boot-starter-test依赖。这样在测试时,就没有必要再添加额外的jar包。 spring-boot-starter-test主要提供了以下测试库。
- JNnit:标准的单元测试Java应用程序
- Spring Test&Spring Boot Test:针对Spring Boot应用程序的单元测试。
- Mockito:Java mocking框架,用于模拟任何Spring管理的Bean,例如在单元测试中模拟一个第三方系统Service接口返回的数据,而不去真正调用第三方系统。
- AssertJ:一个流畅的assertion库,同时也提供了更多的期望值与测试返回值的比较方式。
- JSONassert:对JSON对象或JSON字符串断言的库。
- JsonPath:提供类似于Xpath(一门在XML文档中查找信息的语言)那样的符号来获取JSOn数据片段。
二、Spring Boot单元测试程序模板
@RunWith注解是JUnit标准的一个注解,目的是告诉JUnit框架不要使用内置的方法进行单元测试,而应使用@RunWith指明的类来进行单元测试,所有的Spring单元测试总是使用SpringRunner.class。 @SpringBootTest用于Spring Boot应用测试,它默认根据包名逐级网上找,一直找到Spring Boot主程序(包含@SpringBootApplication注解的类),并在单元测试时启动该主程序来创建Sping上下文环境。
三、测试Service
单元测试Service代码与通过Controller调用Service代码相比,需要特别考虑该Service是否依赖其他还未开发完毕的Service(第三方接口)。如果依赖其他还未开发完毕的Service,我们需要使用Mockito来模拟未完成的Service。 假设,在UserService中依赖CreditService(第三方接口)的getCredit方法获得用户积分
代码语言:javascript复制@Service
public class UserServiceImpl implements UserService{
@AutoWired
private CreditService creditService;
@AutoWired
UserRepository userRepository;
@Override
public int getCredit(Integer uid){
User user=userRepository.getOne(uid);
if(user!=null){
return creditService.getCredit(uid);
}else{
return -1;
}
}
}
那么,我们如何测试UserService呢?问题是单元测试不能实际调用CreditService(因为CreditService是第三方系统),因此,我们在单元测试类需要使用Mockito的注解@MockBean自动注入Spring管理的Service,用来提供模拟实现,在Spring上下文中,CreditService实现已经被模拟实现代替了。
代码语言:javascript复制import org.mockito.BDDMockito;
import org.springframework.boot.test.mock.mockito.MockBean;
@RunWith(SpringRunner.class)
@Transactional
public class UserServiceTest{
//注入要测试的service
@Autowired
private UserService userService;
@MockBean
private CreditService creditService;
@Test
public void testUserService(){
int uid=1;
int expectedCredit=50;
//given是BDDMockito的一个静态方法,用来模拟一个Service方法调用返回,anyInt()表示可以传入任何参数,willReturn方法说明这个调用将返回50
BDDMockito.given(creditService.getCredit(anyInt())).willReturn(expectedCredit);
int credit=userService.getCredit(uid);
//assert定义测试的条件,expectedCredit与credit相等时,assertEquals方法保持沉默,不等时抛出异常。
assertEquals(expectedCredit,credit);
}
}
四、测试Controller
在Spring Boot应用中,可以单独测试Controller代码,用来验证与Controller相关的URL路径映射、文件上传、参数绑定、参数校验等特性。可以通过@WebMvcTest注解来完成Controller单元测试,当然也可以通过@SpringBootTest测试Controller。 通过@WebMvcTest测试Controller得代码模板如下:
代码语言:javascript复制import org.mockito.BDDMockito;
import org.springframework.boot.test.mock.mockito.MockBean;
@RunWith(SpringRunner.class)
//被测试的Controller
@WebMvcTest(UserController.class)
public class UserControllerTest{
//MockMvc是Spring提供的专用于测试Controller的类
@Autowired
private MockMvc mvc;
//用@MockBean模拟实现UserService,这是因为在测试Controller时,Spring容器并不会初始化@Service注解的Service类
@MockBean
private UserService userService;
@Test
public void testMvc(){
int uid=1;
int expectedCredit=50;
//given是BDDMockito的一个静态方法,用来模拟一个Service方法调用返回。这里模拟userService
BDDMockito.given(userService.getCredit(uid)).willReturn(50);
//peform完成一次Controller的调用,Controller测试是一张模拟测试,实际上并未发起一次真正的HTTP请求;get方法模拟了一次Get请求,请求地址为/getCredit/{id},这里的{id}被其后的参数uid代替,因此请求路径是/getCredit/1;andExpect表示期望的返回结果。
mvc.peform(get("/getCredit/{id}",uid))
.andExpect(content().string(String.valueof(expectedCredit)));
}
}
关键点:
需要注意的是,我们在使用@WebMvcTest注解测试Controller时,带有@Service以及别的注解组件类不会自动被扫描注册为Spring容器管理的Bean,而@SpringBootTest注解告诉Spring Boot去寻找一个主配置类(一个带@SpringBootApplication的类),并使用它来启动Spring应用程序上下文,注入所有Bean。另外,还需要注意的是,MockMvc用来在Servlet容器内对Controller进行单元测试,并未真正发起了HTTP请求调用Controller。 @WebMvcTest用于从服务器端对Controller层进行统一测试;如果需要从客户端与应用程序交互时,应该使用@SpringBootTest做集成测试。
五、模拟Controller请求
MockMvc的核心方法是:
代码语言:javascript复制public ResultActions perform(RequestBuilder requestBuilder)
RequestBuilder类可以通过调用MockMvcRequestBuilders的get、post、multipart等方法来模拟Controller请求,常用示例如下: 模拟一个get请求:
代码语言:javascript复制mvc.peform(get("/getCredit/{id}", uid));
模拟一个post请求:
代码语言:javascript复制mvc.peform(post("/getCredit/{id}", uid));
模拟文件上传:
代码语言:javascript复制mvc.peform(multipart("/upload").file("file", "文件内容".getBytes("UTF-8")));
模拟请求参数:
代码语言:javascript复制//模拟提交errorMessage参数
mvc.peform(get("/getCredit/{id}/{uname}", uid, uname).param("errorMessage", "用户名或密码错误"));
代码语言:javascript复制//模拟提交check
mvc.peform(get("/getCredit/{id}/{uname}", uid, uname).param("job", "收银员", "IT" ));
六、比较Controller请求返回的结果
我们知道,MockMvc的perform方法返回ResultActions实例,这个实例代表了请求Controller返回的结果。它提供了一系列andExpect方法来对请求Controller返回的结果进行比较。示例代码如下:
代码语言:javascript复制mvc.peform(get("/getOneUser/10"))
.andExpect(status().isOk()) //期望请求成功,即状态码为200
//期望返回内容是application/json
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
//使用JsonPath比较返回的JSON内容
.andExpect(jsonPath("$.name").value("chenheng")); //检查返回内容
除了上述对请求Controller返回的结果进行比较,还有如下的常见结果比较。 1.比较返回的视图
代码语言:javascript复制mvc.peform(get("/getOneUser/10"))
.andExpect(view().name("/userDetail"));
2.比较模型
代码语言:javascript复制mvc.peform(post("/addOneUser"))
.andExpect(status().isOk())
.andExpect(model().size(1))
.andExpect(model().attributeExists("oneUser"))
.andExpect(model().attribute("oneUser", "chenheng"))
3.比较转发或重定向
代码语言:javascript复制mvc.peform(post("/addOneUser"))
.andExpect(forwardedUrl("/user/selectAll")); //或者 redirectedUrl("/user/selectAll")
4.比较返回的内容
代码语言:javascript复制andExpect(content().string("测试很好玩")); //比较返回的字符串
andExpect(content().xml(xmlContent)); //返回内容是XML,并且与xmlContent(变量)一样
andExpect(content().json(jsonContent)); //返回内容是JSON,并且与jsonContent(变量)一样
七、实例
篇幅较长,见这篇文章:https://blog.csdn.net/qq_43753724/article/details/115559734