Spring Boot+JUnit5+Mockito单元测试
导语:
最近领导要求项目添加单元测试,指定用
JUnit5
和Mockito
,之前没玩过这两个东西,这几天在网上查了很多资料,略懂皮毛,这篇文章打算把这些东西整理出来。
一,单元测试和JUnit
单元测试
要玩这个东西,首先得知道什么是单元测试,这个很重要,我之前一直纠结写单元测试的意义在哪。
单元测试就是针对最小的功能单元编写测试代码,Java程序最小的功能单元就是方法,因此,对Java程序进行单元测试就是针对单个Java方法进行测试。
先了解一下什么是测试驱动开发
所谓测试驱动开发,是指先编写接口,紧接着编写测试。编写完测试后,我们才开始真正编写实现代码。在编写实现过程中,我们一边写一边测,什么时候测试全部通过了,就表示编写的实现完成了。而当接口需要修改时,修改完只要跑一遍单元测试,如果过了,说明修改没有问题。
然而这是一种理想的情况,现实中大部分情况都是我们已经写好了实现的代码,需要对已有的代码进行测试。
在没有用JUnit之前我们通常会使用main()
来运行测试代码,接口实现写完了,用main()
跑一遍,但是使用main()
有很多缺点,一是不能把测试代码分离,二是main()
只能有一个,三是main()
是静态的会影响很多测试,谁用谁知道。
所以我们需要了解一下JUnit
框架。
JUnit框架
JUnit是一个开源的Java语言的单元测试框架,专门针对Java设计,使用最广泛。JUnit目前最新版本是JUnit5。
关于JUnit的使用这里就不写了,网上教程一大把,贴上个比较完整的教程JUnit教程
Maven依赖
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.5.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>
二,Mockito框架
既然是对程序中的某个方法进行测试,难免会遇到方法中有外部依赖的时候,这时候就可以用Mockito来进行模拟外部依赖,比如一个controller中有service调用,这时就可以用Mockito把这个Service对象Mock掉,真正的实现"单元"测试。
关于Mockito使用,这里也贴上教程Mockito教程
三,Controller层测试示例
了解完这两个东西就可以使用他们来测试代码了,首先看下Controller层,在Controller层中我们的每个方法都是一个HTTP请求的入口,所以我们要测试Controller代码需要模拟HTTP请求,使用MockMvc
就可以做到。
MockMvc的使用方式:
- MockMvcBuilder构造MockMvc的构造器
- MockMvcRequestBuilders创建请求request
- mockMvc调用perform,执行一个request请求,调用controller的业务处理逻辑,返回ResultActions
- 可以通过ResultActions, MockMvcResultMatchers对结果进行验证
示例一(GET请求不带参数):
@SpringBootTest()
@Slf4j
public class MockMvcDemo extends AbstractBaseTest {
private MockMvc mvc;
@BeforeEach
public void setUp() {
// (1)构建mvc环境
mvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
}
@Test
public void test() throws Exception {
// (2)构建请求
MockHttpServletRequestBuilder request = MockMvcRequestBuilders.get("/account/info")
.contentType("text/html")
.accept(MediaType.APPLICATION_JSON);
// (3)发送请求,获取请求结果
ResultActions perform = mvc.perform(request);
// (4)请求结果校验
perform.andExpect(MockMvcResultMatchers.status().isOk());
MvcResult mvcResult = perform.andReturn();
MockHttpServletResponse response = mvcResult.getResponse();
// (5)校验返回信息
log.info(response.getContentAsString());
}
}
示例二(POST请求带token和参数对象):
如果是POST请求带参数对象,可以使用MockMvcRequestBuilders.post方法模拟POST请求
@SpringBootTest()
@Slf4j
public class MockMvcDemo extends AbstractBaseTest {
private MockMvc mvc;
@BeforeEach
public void setUp() {
// (1)构建mvc环境
mvc = MockMvcBuilders.standaloneSetup(new AccountController()).build();
}
@Test
public void test() throws Exception {
// (2)构建请求
MockHttpServletRequestBuilder request = MockMvcRequestBuilders.post("/account/info")
.header("Authorization",token)
.contentType(MediaType.APPLICATION_JSON)
.content(JSON.toJSONString(params));
// (3)发送请求,获取请求结果
ResultActions perform = mvc.perform(request);
// (4)请求结果校验
perform.andExpect(MockMvcResultMatchers.status().isOk());
MvcResult mvcResult = perform.andReturn();
MockHttpServletResponse response = mvcResult.getResponse();
// (5)校验返回信息
log.info(response.getContentAsString());
}
}
示例三(使用Mockito模拟掉外部依赖):
Controller中一般都会依赖其他Service服务,可以使用Mockito模拟掉,只专注于Controller层的测试,假设AccountController依赖于AccountService中的service方法,参考以下示例:
@SpringBootTest()
@Slf4j
public class MockMvcDemo extends AbstractBaseTest {
private MockMvc mvc;
@Mock
private AccountService service;
@InjectMocks
private AccountController controller;
@BeforeEach
public void setUp() {
MockitoAnnotations.initMocks(this);//这句话执行以后,service自动注入到controller中。
// (1)构建mvc环境
mvc = MockMvcBuilders.standaloneSetup(controller).build();
}
@Test
public void test() throws Exception {
// (2)模拟外部依赖返回结果
when(service.service(Mockito.any(Param.class))).thenReturn("success");
// (3)构建请求
MockHttpServletRequestBuilder request = MockMvcRequestBuilders.post("/account/info")
.header("Authorization",token)
.contentType(MediaType.APPLICATION_JSON)
.content(JSON.toJSONString(params));
// (4)发送请求,获取请求结果
ResultActions perform = mvc.perform(request);
// (5)请求结果校验
perform.andExpect(MockMvcResultMatchers.status().isOk());
MvcResult mvcResult = perform.andReturn();
MockHttpServletResponse response = mvcResult.getResponse();
// (6)校验返回信息
log.info(response.getContentAsString());
}
}
注意:
有些依赖没法用@InjectMocks来自动注入,可以通过引入ReflectionTestUtils,解决依赖注入的问题。
ReflectionTestUtils.setField(controller,"service",service);