例如:测试 controller时,依赖 service,这个时候就能够假设当调用 service 某个方法时返回指定的某些值,从而来下降引用类所带来的测试复杂度增长的影响。Mockito就用于这种场景。java
Mockito的使用,通常有如下几种组合:web
经过Mockito指定打桩对象的返回值时,能够经过如下方式进行:spring
given用于对指定方法进行返回值的定制,它须要与will开头的方法一块儿使用,will开头的方式根据其接收参数的不一样,又分红两类:一是接收直接值的,如直接指定返回结果为某个常量;二是接收Answer参数的,能够骑过Answer类的answer方法来根据传入参数定制返回结果。json
咱们实际针对的通常是某个类的某个方法;这个方法可能会有输入参数;考虑这种场景:若是要假设打桩的这个方法,在某个输入时返回值A;在另一个输入时返回值为B;这种场景就能够经过Answer类来实现。api
案例 根据传入的参数,返回不一样的数据springboot
@RunWith(SpringRunner.class) @SpringBootTest public class LearnController2Test { @Autowired private WebApplicationContext wac; private MockMvc mvc; private MockHttpSession session; /** * 1. 对于不须要返回的任何值的类的全部方法,能够直接使用MockBean * 2. @MockBean 会代理已有的bean的方法,不会执行真实 bean 的具体方法。 */ @MockBean private LearnService learnService; @Before public void setupMockMvc() { //初始化MockMvc对象 mvc = MockMvcBuilders.webAppContextSetup(wac).build(); //构建session session = new MockHttpSession(); User user = new User("root", "root"); //拦截器那边会判断用户是否登陆,因此这里注入一个用户 session.setAttribute("user", user); } /** * 获取教程测试用例 * <p> * get 请求 * <p> * controller 依赖 service 的方法,这里给 service 方法打桩,不执行真实的方法 * * @throws Exception */ @Test public void qryLearn() throws Exception { LearnResource learnResource = new LearnResource(); learnResource.setUrl("http://www.baidu.com"); learnResource.setTitle("zhang"); learnResource.setAuthor("zhang"); learnResource.setId(10L); // 当调用 selectByKey 函数时,返回指定的值 given(this.learnService.selectByKey(Mockito.any())).willAnswer(new Answer<Object>() { /** * InvocationOnMock 经过它能够获取打桩方法的实际传入参数清单 * @param invocationOnMock * @return * @throws Throwable */ @Override public Object answer(InvocationOnMock invocationOnMock) throws Throwable { Long argumentAt = invocationOnMock.getArgumentAt(0, Long.class); System.out.println("调用方法的实际参数: " + argumentAt); if (argumentAt.equals(Long.parseLong("1001"))) { return learnResource; } return null; } }); mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001") .contentType(MediaType.APPLICATION_JSON_UTF8) .accept(MediaType.APPLICATION_JSON_UTF8) .session(session) ) .andExpect(MockMvcResultMatchers.status().isOk()) //jsonPath用来获取author字段比对是否为嘟嘟MD独立博客,不是就测试不经过 .andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD独立博客")) .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot干货系列")) .andDo(MockMvcResultHandlers.print()); } }
经过willReturn能够直接指定打桩的方法的返回值
案例 在任何场景下,都返回指定的数据session
/** * 获取教程测试用例 * <p> * get 请求 * <p> * controller 依赖 service 的方法,这里给 service 方法打桩,不执行真实的方法 * * @throws Exception */ @Test public void qryLearn() throws Exception { LearnResource learnResource = new LearnResource(); learnResource.setUrl("http://www.baidu.com"); learnResource.setTitle("zhang"); learnResource.setAuthor("zhang"); learnResource.setId(10L); given(this.learnService.selectByKey(Mockito.any())).willReturn(learnResource); mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001") .contentType(MediaType.APPLICATION_JSON_UTF8) .accept(MediaType.APPLICATION_JSON_UTF8) .session(session) ) .andExpect(MockMvcResultMatchers.status().isOk()) //jsonPath用来获取author字段比对是否为嘟嘟MD独立博客,不是就测试不经过 .andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD独立博客")) .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot干货系列")) .andDo(MockMvcResultHandlers.print()); } 异常信息: java.lang.AssertionError: JSON path "$.author" Expected :嘟嘟MD独立博客 Actual :zhang <Click to see difference>
thenReturn与willReturn相似mvc
/** * 获取教程测试用例 * <p> * get 请求 * <p> * controller 依赖 service 的方法,这里给 service 方法打桩,不执行真实的方法 * * @throws Exception */ @Test public void qryLearn() throws Exception { LearnResource learnResource = new LearnResource(); learnResource.setUrl("http://www.baidu.com"); learnResource.setTitle("zhang"); learnResource.setAuthor("zhang"); learnResource.setId(10L); when(this.learnService.selectByKey(Mockito.any())).thenReturn(learnResource); mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001") .contentType(MediaType.APPLICATION_JSON_UTF8) .accept(MediaType.APPLICATION_JSON_UTF8) .session(session) ) .andExpect(MockMvcResultMatchers.status().isOk()) //jsonPath用来获取author字段比对是否为嘟嘟MD独立博客,不是就测试不经过 .andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD独立博客")) .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot干货系列")) .andDo(MockMvcResultHandlers.print()); }
thenAnswer与willAnswer也相似ide
/** * 获取教程测试用例 * <p> * get 请求 * <p> * controller 依赖 service 的方法,这里给 service 方法打桩,不执行真实的方法 * * @throws Exception */ @Test public void qryLearn() throws Exception { LearnResource learnResource = new LearnResource(); learnResource.setUrl("http://www.baidu.com"); learnResource.setTitle("zhang"); learnResource.setAuthor("zhang"); learnResource.setId(10L); when(this.learnService.selectByKey(Mockito.any())).thenAnswer(new Answer<Object>() { @Override public Object answer(InvocationOnMock invocationOnMock) throws Throwable { Long argumentAt = invocationOnMock.getArgumentAt(0, Long.class); System.out.println("调用方法的实际参数: " + argumentAt); if (argumentAt.equals(Long.parseLong("1001"))) { return learnResource; } else if (argumentAt.equals(Long.parseLong("1002"))) { learnResource.setAuthor("keke"); return learnResource; } return null; } }); mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1002") .contentType(MediaType.APPLICATION_JSON_UTF8) .accept(MediaType.APPLICATION_JSON_UTF8) .session(session) ) .andExpect(MockMvcResultMatchers.status().isOk()) //jsonPath用来获取author字段比对是否为嘟嘟MD独立博客,不是就测试不经过 .andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD独立博客")) .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot干货系列")) .andDo(MockMvcResultHandlers.print()); } 异常: 参数为 1001 时 java.lang.AssertionError: JSON path "$.author" Expected :嘟嘟MD独立博客 Actual :zhang <Click to see difference> 参数为 1002 时 java.lang.AssertionError: JSON path "$.author" Expected :嘟嘟MD独立博客 Actual :keke <Click to see difference>
// mock 对象不能是 @MockBean 生成的,@MockBean请况下不能用 @Test public void testAnswer1() { List<String> mock = Mockito.mock(List.class); Mockito.doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocationOnMock) throws Throwable { Object[] args = invocationOnMock.getArguments(); System.out.println(args[0]); Integer num = (Integer) args[0]; if (num > 3) { return "大于三"; } else { return "小于三"; } } }).when(mock).get(Mockito.anyInt()); // 当 索引为 4 时,指望 大于三 Assert.assertThat(mock.get(4), equalTo("大于三")); // 当 索引为 2 时,指望 小于三 Assert.assertThat(mock.get(4), equalTo("小于三")); } // mock 对象不能是 @MockBean 生成的,@MockBean请况下不能用 @Test public void testAnswer1() { List<String> mock = Mockito.mock(List.class); Mockito.doReturn("大于三").when(mock).get(Mockito.anyInt()); // 当 索引为 2 时 Assert.assertThat(mock.get(2), equalTo("大于三")); }
@Test public void qryLearn() throws Exception { LearnResource learnResource = new LearnResource(); learnResource.setUrl("http://www.baidu.com"); learnResource.setTitle("zhang"); learnResource.setAuthor("zhang"); learnResource.setId(10L); given(this.learnService.selectByKey(Mockito.any())).willReturn(learnResource); mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001") .contentType(MediaType.APPLICATION_JSON_UTF8) .accept(MediaType.APPLICATION_JSON_UTF8) .session(session) ) .andExpect(MockMvcResultMatchers.status().isOk()) .andDo(MockMvcResultHandlers.print()); // 判断 learnService.selectByKey 方法 是否调用了 Mockito.verify(learnService).selectByKey(1001L); // 判断 learnService.selectByKey 方法,指望调用 2 次,能过 times 函数指定 selectByKey 函数指望调用几回 // 也能够经过 Mockito.atLeast 最少几回,Mockito.atMost 是多几回 等函数判断 Mockito.verify(learnService, Mockito.times(2)).selectByKey(1001L); } 异常:由于 learnService.selectByKey 方法,调用了1次,而指望调用两次,因此测试出错 org.mockito.exceptions.verification.TooLittleActualInvocations: learnServiceImpl bean.selectByKey(1001); Wanted 2 times: -> at com.dudu.outher.LearnController7Test.qryLearn(LearnController7Test.java:86) But was 1 time: -> at com.dudu.controller.LearnController.qryLearn(LearnController.java:88)
given+willThrow函数
@Test public void qryLearn() throws Exception { LearnResource learnResource = new LearnResource(); learnResource.setUrl("http://www.baidu.com"); learnResource.setTitle("zhang"); learnResource.setAuthor("zhang"); learnResource.setId(10L); // 调用 learnService.selectByKey 方法时,抛出异常 given(this.learnService.selectByKey(Mockito.any())).willThrow(new Exception("查询出错")); mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001") .contentType(MediaType.APPLICATION_JSON_UTF8) .accept(MediaType.APPLICATION_JSON_UTF8) .session(session) ) .andExpect(MockMvcResultMatchers.status().isOk()) .andDo(MockMvcResultHandlers.print()); } 异常: org.mockito.exceptions.base.MockitoException: Checked exception is invalid for this method! Invalid: java.lang.Exception: 查询出错
when+thenThrow
@Test public void qryLearn() throws Exception { LearnResource learnResource = new LearnResource(); learnResource.setUrl("http://www.baidu.com"); learnResource.setTitle("zhang"); learnResource.setAuthor("zhang"); learnResource.setId(10L); when(this.learnService.selectByKey(Mockito.any())).thenThrow(new Exception("查询出错")); mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001") .contentType(MediaType.APPLICATION_JSON_UTF8) .accept(MediaType.APPLICATION_JSON_UTF8) .session(session) ) .andExpect(MockMvcResultMatchers.status().isOk()) //jsonPath用来获取author字段比对是否为嘟嘟MD独立博客,不是就测试不经过 .andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD独立博客")) .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot干货系列")) .andDo(MockMvcResultHandlers.print()); } 异常: org.mockito.exceptions.base.MockitoException: Checked exception is invalid for this method! Invalid: java.lang.Exception: 查询出错
doThrow+when
不能用于 @MockBean 场景下
@Test public void testAnswer1() { List<String> mock = Mockito.mock(List.class); // 调用 mock.size 时,抛出指望的异常信息 Mockito.doThrow(new Exception("查询出错")).when(mock).size(); // 调用 mock 对象的方法 mock.size(); } 异常: org.mockito.exceptions.base.MockitoException: Checked exception is invalid for this method! Invalid: java.lang.Exception: 查询出错
doThrow:在模拟对象中调用方法时想要抛出异常时使用.
doReturn:在执行方法时要返回返回值时使用.
doAnswer:须要对传递给方法的参数执行一些操做
doNothing:是最简单的列表,基本上它告诉Mockito在调用模拟对象中的方法时什么也不作.有时用于void返回方法或没有反作用的方法,或者与您正在进行的单元测试无关
https://blog.csdn.net/icarusliu/article/details/78860351
Mockito没法对静态方法进行Mock,若是须要Mock静态方法,须要使用到PowerMockito
<dependency> <groupId>org.powermock</groupId> <artifactId>powermock-api-mockito</artifactId> <version>1.7.1</version> </dependency> <dependency> <groupId>org.powermock</groupId> <artifactId>powermock-module-junit4</artifactId> <version>1.7.1</version> </dependency>
单元测试时,须要使用PowerMockRunner及PrepareForTest两个注解
@RunWith(PowerMockRunner.class) // 对 StringUtils 静态方法进行测试 @PrepareForTest({StringUtils.class}) public class TestStatic { @Test public void testStaticMethod() { // 对 StringUtils 打桩 PowerMockito.mockStatic(StringUtils.class); PowerMockito.when(StringUtils.isNoneBlank(Mockito.anyString())).thenReturn(false); boolean bbb = StringUtils.isNoneBlank("bbb"); System.out.println(bbb); } }
SpringBootTest必需要使用SpringRunner才能生效;但RunWith没有办法指定多个,能够经过PowerMockRunnerDelegate来解决这个问题:
@RunWith(PowerMockRunner.class)//使用powermock提供的代理来使用 @PowerMockRunnerDelegate(SpringRunner.class) @PowerMockIgnore({"javax.management.*", "javax.net.ssl.*"})//忽略一些powermock使用的classloader没法处理的类 @PrepareForTest({StringUtils.class})// @PrepareForTest 能够 mock 多个静态方法 @SpringBootTest public class LearnController11Test { @Autowired private WebApplicationContext wac; private MockMvc mvc; private MockHttpSession session; @MockBean private LearnService learnService; @Before public void setupMockMvc() { //初始化MockMvc对象 mvc = MockMvcBuilders.webAppContextSetup(wac).build(); //构建session session = new MockHttpSession(); User user = new User("root", "root"); //拦截器那边会判断用户是否登陆,因此这里注入一个用户 session.setAttribute("user", user); } /** * 获取教程测试用例 * <p> * get 请求 * <p> * controller 依赖 service 的方法,这里给 service 方法打桩,不执行真实的方法 * * @throws Exception */ @Test public void qryLearn() throws Exception { LearnResource learnResource = new LearnResource(); learnResource.setUrl("http://www.baidu.com"); learnResource.setTitle("Spring Boot干货系列"); learnResource.setAuthor("嘟嘟MD独立博客"); learnResource.setId(10L); // 对 service层中的方法进行 mock given(this.learnService.selectByKey(Mockito.any())).willReturn(learnResource); // 对 StringUtils 打桩,mock 静态方法 PowerMockito.mockStatic(StringUtils.class); // 当 执行 StringUtils.isNoneBlank 方法时,返回 false PowerMockito.when(StringUtils.isNoneBlank(Mockito.anyString())).thenReturn(false); // 实际使用中 StringUtils.isNoneBlank("bbb") 返回 true,但这里返回 false boolean result = StringUtils.isNoneBlank("bbb"); System.out.println("StringUtils.isNoneBlank: " + result); mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001") .contentType(MediaType.APPLICATION_JSON_UTF8) .accept(MediaType.APPLICATION_JSON_UTF8) .session(session) ) .andExpect(MockMvcResultMatchers.status().isOk()) //jsonPath用来获取author字段比对是否为嘟嘟MD独立博客,不是就测试不经过 .andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD独立博客")) .andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot干货系列")) .andDo(MockMvcResultHandlers.print()); } }