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 { @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());
} }

given + willReturn

通过willReturn可以直接指定打桩的方法的返回值

案例 在任何场景下,都返回指定的数据

/**
* 获取教程测试用例
* <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>

when + thenReturn

thenReturn与willReturn类似

    /**
* 获取教程测试用例
* <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());
}

when + thenAnswer/then

thenAnswer与willAnswer也类似

    /**
* 获取教程测试用例
* <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>

doAnswer/doReturn + when

// 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一起使用

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());
} }

spring-boog-测试打桩-Mockito的更多相关文章

  1. Spring MVC测试框架

    原文链接:http://jinnianshilongnian.iteye.com/blog/2004660 Spring MVC测试框架详解——服务端测试 博客分类: springmvc杂谈 spri ...

  2. Spring MVC测试框架详解——服务端测试

    随着RESTful Web Service的流行,测试对外的Service是否满足期望也变的必要的.从Spring 3.2开始Spring了Spring Web测试框架,如果版本低于3.2,请使用sp ...

  3. Spring TestContext测试框架搭建

    同样是测试,JUnit和Spring TestContext相比,Spring TestContext优势如下: 1.Spring TestContext可以手动设置测试事务回滚,不破坏数据现场 2. ...

  4. Spring引用测试

    上下文 using System; using Spring.Core; using Spring.Aop; using System; using Spring.Core; using Spring ...

  5. 用IntelliJ IDEA 开发Spring+SpringMVC+Mybatis框架 分步搭建三:配置spring并测试

    这一部分的主要目的是 配置spring-service.xml  也就是配置spring  并测试service层 是否配置成功 用IntelliJ IDEA 开发Spring+SpringMVC+M ...

  6. Spring Boot(七):spring boot测试介绍

    首先maven要引入spring-boot-starter-test这个包. 先看一段代码 @RunWith(SpringRunner.class) @SpringBootTest(webEnviro ...

  7. spring 学习(一):使用 intellijIDEA 创建 maven 工程进行 Spring ioc 测试

    spring学习(一):使用 intellijIDEA 创建 maven 工程进行 Spring ioc 测试 ioc 概念 控制反转(Inversion of Control,缩写为IOC),是面向 ...

  8. Spring Boot 测试时的日志级别

    1.概览 该教程中,我将向你展示:如何在测试时设置spring boot 日志级别.虽然我们可以在测试通过时忽略日志,但是如果需要诊断失败的测试,选择正确的日志级别是非常重要的. 2.日志级别的重要性 ...

  9. spring + junit 测试

    spring + junit 测试 需要一个工具类 package com.meizu.fastdfsweb; import org.junit.runner.RunWith; import org. ...

  10. Spring Boot中采用Mockito来mock所测试的类的依赖(避免加载spring bean,避免启动服务器)

    最近试用了一下Mockito,感觉真的挺方便的.举几个应用实例: 1,需要测试的service中注入的有一个dao,而我并不需要去测试这个dao的逻辑,只需要对service进行测试.这个时候怎么办呢 ...

随机推荐

  1. P4101 [HEOI2014]人人尽说江南好

    题目描述 小 Z 是一个不折不扣的 ZRP(Zealot Round-game Player,回合制游戏狂热玩家),最近他 想起了小时候在江南玩过的一个游戏. 在过去,人们是要边玩游戏边填词的,比如这 ...

  2. P2325 [SCOI2005]王室联邦

    题目描述 “余”人国的国王想重新编制他的国家.他想把他的国家划分成若干个省,每个省都由他们王室联邦的一个成员来管理. 他的国家有n个城市,编号为1..n.一些城市之间有道路相连,任意两个不同的城市之间 ...

  3. Post Lamps CodeForces - 990E(暴力出奇迹?)

    题意: 在一个从0开始的连续区间上  放置几个小区间,使得这些小区间覆盖整个大区间,不同长度的小区间有不同的花费,其中有m个点,小区间的左端点不能放在这些点上 解析: 显然如果0是这m点中的一个 则无 ...

  4. 【HDU4336】Card Collector(Min-Max容斥)

    [HDU4336]Card Collector(Min-Max容斥) 题面 Vjudge 题解 原来似乎写过一种状压的做法,然后空间复杂度很不优秀. 今天来补一种神奇的方法. 给定集合\(S\),设\ ...

  5. BZOJ 2243 染色 | 树链剖分模板题进阶版

    BZOJ 2243 染色 | 树链剖分模板题进阶版 这道题呢~就是个带区间修改的树链剖分~ 如何区间修改?跟树链剖分的区间询问一个道理,再加上线段树的区间修改就好了. 这道题要注意的是,无论是线段树上 ...

  6. Linux下cp ~中关于“~”的疑问

    目的:将wi主文件夹下的.bashrc复制到/tmp,并更名为bashrc.对于书上的代码上加“~”存在疑问. cp ~/.bashrc /tmp/bashrc 对命令进行了以下尝试: 为什么不加“~ ...

  7. java多线程 -- ReadWriteLock 读写锁

    写一条线程,读多条线程能够提升效率. 写写/读写 需要“互斥”;读读 不需要互斥. ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作.只要没有 writer,读取锁 ...

  8. 解题:POI 2008 Subdivision of Kingdom

    题面 还可以这么搜......学到了(PoPoQQQ orz) 我们最朴素的做法是枚举所有状态(当然可以剪,剪完最终实际状态量也是$C_{26}^{13}$的),然后每次$O(n)$扫一遍判断,大概会 ...

  9. 【线段树分治】【P5227】 [AHOI2013]连通图

    Description 给定一个无向连通图和若干个小集合,每个小集合包含一些边,对于每个集合,你需要确定将集合中的边删掉后改图是否保持联通.集合间的询问相互独立 定义一个图为联通的当且仅当对于任意的两 ...

  10. Matlab周期图法使用FFT实现

    参考文章:http://www.cnblogs.com/adgk07/p/9314892.html 首先根据他这个代码和我之前手上已经拥有的那个代码,编写了一个适合自己的代码. 首先模仿他的代码,测试 ...