当前位置: 代码迷 >> 综合 >> spring-boog-测试打桩-Mockito
  详细解决方案

spring-boog-测试打桩-Mockito

热度:60   发布时间:2024-03-08 12:12:52.0

Mockito用于测试时进行打桩处理;通过它可以指定某个类的某个方法在什么情况下返回什么样的值。

例如:测试 controller时,依赖 service,这个时候就可以假设当调用 service 某个方法时返回指定的某些值,从而来降低引用类所带来的测试复杂度增加的影响。Mockito就用于这种场景。

Mockito常用测试场景描述如下:

  • 指定打桩对象的返回值
  • 判断某个打桩对象的某个方法被调用及调用的次数
  • 指定打桩对象抛出某个特定异常

Mockito的使用,一般有以下几种组合:

  • do/when:包括doThrow(…).when(…)/doReturn(…).when(…)/doAnswer(…).when(…)
  • given/will:包括given(…).willReturn(…)/given(…).willAnswer(…)
  • when/then: 包括when(…).thenReturn(…)/when(…).thenAnswer(…)

指定打桩对象返回值

通过Mockito指定打桩对象的返回值时,可以通过以下方式进行:

given

given用于对指定方法进行返回值的定制,它需要与will开头的方法一起使用,will开头的方式根据其接收参数的不同,又分成两类:一是接收直接值的,如直接指定返回结果为某个常量;二是接收Answer参数的,可以骑过Answer类的answer方法来根据传入参数定制返回结果。

Answer对象

我们实际针对的一般是某个类的某个方法;这个方法可能会有输入参数;考虑这种场景:如果要假设打桩的这个方法,在某个输入时返回值A;在另外一个输入时返回值为B;这种场景就可以通过Answer类来实现。

given + willAnswer/will

案例 根据传入的参数,返回不同的数据

@RunWith(SpringRunner.class)
@SpringBootTest
public class LearnController2Test {@Autowiredprivate WebApplicationContext wac;private MockMvc mvc;private MockHttpSession session;/*** 1. 对于不需要返回的任何值的类的所有方法,可以直接使用MockBean* 2. @MockBean 会代理已有的bean的方法,不会执行真实 bean 的具体方法。*/@MockBeanprivate LearnService learnService;@Beforepublic void setupMockMvc() {//初始化MockMvc对象mvc = MockMvcBuilders.webAppContextSetup(wac).build();//构建sessionsession = new MockHttpSession();User user = new User("root", "root");//拦截器那边会判断用户是否登录,所以这里注入一个用户session.setAttribute("user", user);}/*** 获取教程测试用例* <p>* get 请求* <p>* controller 依赖 service 的方法,这里给 service 方法打桩,不执行真实的方法** @throws Exception*/@Testpublic 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*/@Overridepublic 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());}}

given + willReturn

通过willReturn可以直接指定打桩的方法的返回值
案例 在任何场景下,都返回指定的数据

/*** 获取教程测试用例* <p>* get 请求* <p>* controller 依赖 service 的方法,这里给 service 方法打桩,不执行真实的方法** @throws Exception*/@Testpublic 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>

when + thenReturn

thenReturn与willReturn类似

    /*** 获取教程测试用例* <p>* get 请求* <p>* controller 依赖 service 的方法,这里给 service 方法打桩,不执行真实的方法** @throws Exception*/@Testpublic 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());}

when + thenAnswer/then

thenAnswer与willAnswer也类似

    /*** 获取教程测试用例* <p>* get 请求* <p>* controller 依赖 service 的方法,这里给 service 方法打桩,不执行真实的方法** @throws Exception*/@Testpublic 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>() {@Overridepublic 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>

doAnswer/doReturn + when

// mock 对象不能是 @MockBean 生成的,@MockBean请况下不能用
@Testpublic void testAnswer1() {List<String> mock = Mockito.mock(List.class);Mockito.doAnswer(new Answer() {@Overridepublic 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请况下不能用@Testpublic void testAnswer1() {List<String> mock = Mockito.mock(List.class);Mockito.doReturn("大于三").when(mock).get(Mockito.anyInt());// 当 索引为 2 时Assert.assertThat(mock.get(2), equalTo("大于三"));}

判断某个打桩对象的某个方法被调用及调用的次数

 @Testpublic 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

@Testpublic 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

@Testpublic 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 场景下

@Testpublic 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 {@Testpublic 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一起使用

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 {@Autowiredprivate WebApplicationContext wac;private MockMvc mvc;private MockHttpSession session;@MockBeanprivate LearnService learnService;@Beforepublic void setupMockMvc() {//初始化MockMvc对象mvc = MockMvcBuilders.webAppContextSetup(wac).build();//构建sessionsession = new MockHttpSession();User user = new User("root", "root");//拦截器那边会判断用户是否登录,所以这里注入一个用户session.setAttribute("user", user);}/*** 获取教程测试用例* <p>* get 请求* <p>* controller 依赖 service 的方法,这里给 service 方法打桩,不执行真实的方法** @throws Exception*/@Testpublic 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层中的方法进行 mockgiven(this.learnService.selectByKey(Mockito.any())).willReturn(learnResource);// 对 StringUtils 打桩,mock 静态方法PowerMockito.mockStatic(StringUtils.class);// 当 执行 StringUtils.isNoneBlank 方法时,返回 falsePowerMockito.when(StringUtils.isNoneBlank(Mockito.anyString())).thenReturn(false);// 实际使用中 StringUtils.isNoneBlank("bbb") 返回 true,但这里返回 falseboolean 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());}}

博客所有内容仅供自已学习和学习过程的记录,如有侵权,请联系我删除!!!

分类: spring boot

标签: spring boot

好文要顶 关注我 收藏该文  

张建斌
关注 - 38
粉丝 - 59

+加关注

0

0

? 上一篇: spring-boot-单元测试
? 下一篇: docker mysql 主从

posted @ 2018-12-07 15:45  张建斌  阅读(1107)  评论(0)  编辑  收藏

  相关解决方案