单元测试可以使用springboot自带的单元测试依赖:
我使用的是2.5.2版本,不同版本的mockito的初始化方法可能会有变化主要是两个:
openMocks 和initMocks看springboot版本而定,高版本都会是openMocks。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency>
@Test:使用该注解标注的public void方法会表示为一个测试方法;
@Ingore:表示这个方法会被忽略执行;
@BeforeClass:表示在类中的任意public static void方法执行之前执行;
@AfterClass:表示在类中的任意public static void方法之后执行;
@Before:表示在任意使用@Test注解标注的public void方法执行之前执行;
@After:表示在任意使用@Test注解标注的public void方法执行之后执行;
所谓的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数据。
这个有个额外的注意版本对照需要一致不然会报错版本对照如下:
版本对照