单元测试可以使用springboot自带的单元测试依赖:
1.首先是依赖
我使用的是2.5.2版本,不同版本的mockito的初始化方法可能会有变化主要是两个:
openMocks 和initMocks看springboot版本而定,高版本都会是openMocks。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
2.单元测试常用的几个注解:
@Test:使用该注解标注的public void方法会表示为一个测试方法;
@Ingore:表示这个方法会被忽略执行;
@BeforeClass:表示在类中的任意public static void方法执行之前执行;
@AfterClass:表示在类中的任意public static void方法之后执行;
@Before:表示在任意使用@Test注解标注的public void方法执行之前执行;
@After:表示在任意使用@Test注解标注的public void方法执行之后执行;
3.Mock的概念:
所谓的mock就是创建一个类的虚假的对象,在测试环境中,用来替换掉真实的对象,以达到两大目的:
验证这个对象的某些方法的调用情况,调用了多少次,参数是什么等等
指定这个对象的某些方法的行为,返回特定的值,或者是执行特定的动作
使用Mock之前,需要在@Before或@BeforeClass对应的方法中添加如下,表示添加mock注解初始化。
MockitoAnnotations.initMocks(this);
一般使用mock模型的话还会用到如下几个注解:
@InjectMocks:通过创建一个实例,它可以调用真实代码的方法,其余用@Mock(或@Spy)注解创建的mock将被注入到用该实例中。
@Mock:对函数的调用均执行mock(即虚假函数),不执行真正部分。
@Spy:对函数的调用均执行真正部分。
Mockito中的Mock和Spy都可用于拦截那些尚未实现或不期望被真实调用的对象和方法,并为其设置自定义行为。二者的区别在于Mock不真实调用,Spy会真实调用。
那么概念了解完了,如何使用呢?请往下看:
4.测试用例的描述:
@Mock和@Spy使用:
外部类:TestCom的方法:
public int test(int a, int b){
log.info("testCome.到我这了");
return a+b;
}
被测试类:EmployeeService 方法:
@Resource
private TestCom testCom;
public int test(int a, int b){
log.info("到我这了");
return testCom.test(a, b);
}
测试方法:
@Mock
private TestCom testCom;
@InjectMocks
private EmployeeService employeeService;
@Before
public void init() {
MockitoAnnotations.openMocks(this);
System.out.println("开始测试-----------------");
}
@Test
public void test(){
when(testCom.test(anyInt(),anyInt())).thenReturn(7);
int test = employeeService.test(1, 1);
log.info("计算结果:{}",test);
Assert.assertEquals(7,test);
}
(1)这样调用的话是不会打印出:testCome.到我这了,但是会返回结果7,因为mock数据是返回的是7 最后的测试结果是 true
执行结果如下:
开始测试-----------------
2021-09-22 09:31:48.602 INFO 82620 — [ main] c.t.g.unitdemo.service.EmployeeService : 到我这了
2021-09-22 09:31:48.618 INFO 82620 — [ main] com.test.gcy.unitdemo.SuitTestTwo : 计算结果:7
测试结束-----------------
(2)如果把Mock改为Spy的话是会执行TestCom的test方法的但是还是会返回结果7,这就是mock和spy的区别。
执行结果如下:
开始测试-----------------
2021-09-22 09:35:25.604 INFO 83287 — [ main] com.test.gcy.unitdemo.service.TestCom : testCome.到我这了
2021-09-22 09:35:25.615 INFO 83287 — [ main] c.t.g.unitdemo.service.EmployeeService : 到我这了
2021-09-22 09:35:25.620 INFO 83287 — [ main] com.test.gcy.unitdemo.SuitTestTwo : 计算结果:7
测试结束-----------------
注意一点:针对打桩的时候尽量使用org.mockito.ArgumentMatchers包下的模拟参数,这样会匹配任何打桩,否则如果你打桩的传递参数是A,实际测试调用的时候是B,就会打桩失效,不会返回mock数据。
额外介绍一下从controller层做单元测试的链路:
1.使用mockmvc去跑整个链路:
被测试的controller:EmployeeController
测试类:UnitdemoApplicationTests
初始化调用上下文:
private MockMvc mockMvc;
@Autowired
private WebApplicationContext context;
@Before
public void setUp() throws Exception {
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
}
从接口层调用:
@Test
public void testMock() throws Exception {
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/name")
.param("title", "demo"))
.andExpect(MockMvcResultMatchers.status().is(HttpStatus.OK.value()))
.andDo(print()).andReturn();
log.info("{}",mvcResult.getResponse().getContentAsString());
}
这样调用链路会包含了controller层;
2.使用模板也可以去跑整个链路:
@Autowired
private TestRestTemplate restTemplate;
@Test
public void getName() {
String name = restTemplate.getForObject("/name?title=1111", String.class);
System.out.println(name + "--- " +test);
Assert.assertNotNull(name);
}
这样也是可以调用接口层面的单元测试。
静态方法调用http的方式请求别人的接口也是可以使用mock的:
依赖:
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>2.0.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>2.0.2</version>
<scope>test</scope>
</dependency>
工具类:TestUtils:
public static String test(String body){
log.info("body:{}",body);
return body;
}
测试类:MockStaticMethodTest
@Test
public void Test() {
PowerMockito.mockStatic(TestUtils.class);
PowerMockito.when(TestUtils.test(anyString())).thenReturn("hahah");
log.info(TestUtils.test("xxx"));
}
这个mock静态方法是和@Mock注解是一样的对于被打桩的类调用的方法是不会执行的,指挥返回mock数据。
这个有个额外的注意版本对照需要一致不然会报错版本对照如下:
版本对照