常用注解
@Mock | 模拟 | 创建一个对象,但无法进行实际操作 |
---|---|---|
@InjectMocks | 注射 | 用于mock时插入相应的依赖项 |
@Captor | 捕获 | 用于捕获方法的参数值,来用于进一步做断言验证 |
@Spy | 间谍 | 创建一个真实对象并监视这个对象 |
常用方法
doReturn() | 返回 | doReturn(值).when(条件); |
---|---|---|
doThrow() | 抛异常 | doThrow(异常).when(条件); |
doAnswer() | 回复 | doAnswer(值).when(条件); |
doNothing() | 无操作 | doNothing().when(条件); |
doCallRealMethod() | 使用真实方法操作 | doCallRealMethod().when(对象).delete(); |
你需要对传递给方法的参数执行一些操作,例如,添加一些值,进行一些计算,甚至修改它们doAnswer给你提供了在方法被调用的瞬间执行的Answer接口,这个接口允许你通过InvocationOnMock参数与参数交互。另外,answer方法的返回值将是mocked方法的返回值。
initMocks 【初始化】MockitoAnnotations.initMocks(this);
assertThat 【断言】assertThat(实际值, is(理想值));
when 【定义返回值】when(操作).thenReturn(值).thenReturn(值)
Mockito 中文文档 ( 2.23.4 )
本文档参考hehonghui/mockito-doc-zh项目,如有侵权,请联系删除
Mockito库能够Mock对象、验证结果以及打桩(stubbing)。
该文档您也可以通过http://mockito.org获取到。所有文档都保存在javadocs中,因为它能够保证文档与源代码的一致性。这样也能够让离线的用户从IDE直接访问到文档。这样一来也能够激励Mockito开发者在每次写代码、每次提交时更新对应的文档。
目录
- 迁移到Mockito 2.0
0.1 Mockito支持Android
0.2 免配置内联mock making - 验证某些行为
- 如何做一些测试桩 (Stub)
- 参数匹配器 (matchers)
- 验证函数的确切、最少、从未调用次数
- 通过打桩为无返回值函数抛出异常
- 验证执行执行顺序
- 确认交互(interaction)操作没有在mock对象上执行
- 查找多余的调用
- 简化mock对象的创建 - @Mock注解
- 为连续的调用打桩 (迭代器风格的打桩)
- 通过回调方式来打桩
- doReturn()、doThrow()、doAnswer()、doNothing()、doCallRealMethod()系列方法
- 监控真实对象
- 修改没有测试桩的调用的默认返回值 ( 1.7版本之后 )
- 为下一步的断言捕获参数 (1.8版本之后)
- 真实的局部模拟对象(mocks) (1.8版本之后)
- 重置mocks对象 (1.8版本之后)
- 故障排查与验证框架的使用 (1.8版本之后)
- 行为驱动开发的别名 (1.8版本之后)
- 可序列化的mocks(1.8.1版本之后)
- 新的注解 : @Captor,@Spy,@InjectMocks (1.8.3版本之后)
- 带超时的验证 (1.8.5版本之后)
- 自动实例化被@Spies, @InjectMocks注释的字段以及构造函数注入 (1.9.0版本之后)
- 单行测试桩 (1.9.0版本之后)
- 验证被忽略的测试桩 (1.9.0版本之后)
- mock详情 (2.2.x中改进)
- 真实实例的委托调用 (1.9.5版本之后)
- MockMaker API (1.9.5版本之后)
- BDD风格的验证 (1.10.0版本之后)
- 监视 或 模拟 抽象类 (1.10.12版本加入,在2.7.13 和 2.7.14版特征得到增强))
- Mockito的模拟对象 可以通过 classloaders 序列化/反序列化(1.10.0版本之后)
- Deep stubs 更好的泛型支持 (1.10.0版本之后)
- Mockito JUnit 规则 (1.10.17版本之后)
- 开启和关闭插件的开关 (1.10.15版本之后)
- 自定义验证失败消息 (2.1.0版本之后)
- Java8 Lambda匹配器的支持 (2.1.0版本之后)
- Java8 自定义Answer的支持 (2.1.0版本之后)
- 元信息和泛型信息保留 (2.1.0版本之后)
- Mocking final类型,枚举和final方法 (2.1.0版本之后)
- “严格的”Mocktio能提高生产效率并使测试用例更清晰(2.+版本之后)
- 框架集成的高级公开API (2.10.+版本之后)
- 集成新的API: 监听验证开始(verification start)事件(2.11.+版本之后)
- 集成新的API: 测试框架支持MockitoSession(2.15.+版本之后)
- org.mockito.plugins.InstantiatorProvider泄露内部API所以被org.mockito.plugins.InstantiatorProvider2替代(2.15.4版本之后)
- JUnit5+的扩展
- 新的Mockito.lenient()和MockSettings.lenient()方法(2.20.0版本之后)
0. 迁移到Mockito 2.0
为了持续提升Mockito以及更进一步的提升单元测试体验,我们希望你升级到Mockito 2.0.Mockito遵循语意化的版本控制,除非有非常大的改变才会变化主版本号。在一个库的生命周期中,为了引入一系列有用的特性,修改已存在的行为或者API等重大变更是在所难免的。因此,我们希望你能够爱上 Mockito 2.0!
重要变更 :
- Mockito从Hamcrest中解耦,自定义的matchers API也发生了改变,查看ArgumentMatcher 的基本原理以及迁移指南。
跟着我们的示例来mock 一个List,因为大家都知道它的接口(例如add(),get(), clear())。不要mock一个真实的List类型,使用一个真实的实例来替代。
0.1. Mockito支持Android
在2.6.1版本,我们提供了”原生的“Android支持。通过在项目中新增”mockito-android“库依赖,就能得到支持。这个工件发布到同一个Mockito组织,并且可以按照以下方式导入Android:
repositories {
jcenter()
}
dependencies {
testCompile "org.mockito:mockito-core:+"
androidTestCompile "org.mockito:mockito-android:+"
}
通过在“testCompile”范围中使用“mockito-core”工件,可以在常规VM上继续运行相同的单元测试,如上所示。请注意,由于Android VM的限制,您不能在Android上使用内联模拟生成器。如果您在Android上遇到模拟的问题,请在官方问题跟踪器上打开一个issue。一定要提供您正在使用的Android版本和项目的依赖项。
0.2. 免配置内联mock making
从2.7.6版本开始,我们提供”mockito-inline“ artifact,它支持内联的mock making而不需要配置MockMaker的扩自文件。使用方式,用”mockito-inline“替换”mockito-core“:
repositories {
jcenter()
}
dependencies {
testCompile "org.mockito:mockito-inline:+"
}
注意,这个artifact可能会停止维护,当内联mock making特性被集成到默认的mock maker里时。
查看更多关于内联mock making,查看39章
1. 验证某些行为
// 静态导入会使代码更简洁
import static org.mockito.Mockito.*;
// mock creation 创建mock对象
List mockedList = mock(List.class);
//using mock object 使用mock对象
mockedList.add("one");
mockedList.clear();
//verification 验证
verify(mockedList).add("one");
verify(mockedList).clear();
一旦mock对象被创建了,mock对象会记住所有的交互动作。然后你就可以选择性的验证你感兴趣的交互动作。
2. 如何做一些测试桩 (Stub)
// 你可以mock具体的类,不仅只是接口
LinkedList mockedList = mock(LinkedList.class);
// 测试桩
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(1)).thenThrow(new RuntimeException());
// 输出“first”
System.out.println(mockedList.get(0));
// 抛出异常
System.out.println(mockedList.get(1));
// 因为get(999) 没有打桩,因此输出null
System.out.println(mockedList.get(999));
//虽然能验证被打桩对象的调用情况,但这通常写起来很啰嗦
//如果你关注get(0)的返回值,那么其他地方就会中断(通常在verify()执行之前)
//如果你不关注get(0)的返回值,那么它就不应该被打桩。不相信?看下面例子。
verify(mockedList).get(0);
- 默认情况下,所有的函数都有返回值。mock函数会适当的返回null,原始类型/原始类型的包装类,一个空的集合,例如int/Integer返回0、boolean/Boolean返回false;
- 打桩动作可以被覆写 : 例如常见的打桩动作可以用于公共的配置,然后在测试函数中能够重新打桩。请注意,覆写测试桩函数是一种潜在的代码异味,它指出打桩动作太多了;
- 一旦测试桩函数被调用,该函数将会一直返回固定的值;
- 最后一个打桩动作是很重要的 - 当你用相同参数为一个方法做多次打桩时。换句话说:打桩的顺序很重要,但是这很少有意义,例如,当为完全相同的方法调用打桩,或者当使用参数匹配器时,等等。
3. 参数匹配器 (matchers)
Mockito以自然的java风格来验证参数值: 使用equals()函数。有时,当需要额外的灵活性时你可能需要使用参数匹配器,也就是argument matchers :
// 使用内置的anyInt()参数匹配器来打桩
when(mockedList.get(anyInt())).thenReturn("element");
// 使用自定义的参数匹配器来打桩( 在isValid()函数中返回你自己的匹配器实现 )
when(mockedList.contains(argThat(isValid()))).thenReturn("element");
// 输出“element”
System.out.println(mockedList.get(999));
// 你也可以用参数匹配器来验证
verify(mockedList).get(anyInt());
//参数匹配器也能用Java 8 Lambda风格编写
verify(mockedList).add(argThat(someString -> someString.length() > 5));
参数匹配器使验证和打桩变得更灵活。点击这里或这里查看更多内置的匹配器以及自定义参数匹配器或者hamcrest 匹配器的示例。
如果仅仅是获取自定义参数匹配器的信息,查看ArgumentMatcher类文档即可。
为了合理的使用复杂的参数匹配,使用原生风格的equals()匹配器,偶尔也用下anyX()匹配器,会使得测试代码更简洁、简单。有时,会迫使你重构代码以使用equals()匹配或者实现equals()函数来帮助你进行测试。
同时建议你阅读第15章节或者ArgumentCaptor类文档。ArgumentCaptor是一个能够捕获参数值的特殊参数匹配器。
参数匹配器的注意点 :
如果你使用了参数匹配器, 那么所有参数都要用匹配器。
示例 : ( 该示例展示了如何多次应用于测试桩函数的验证 )
verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
// 上述代码是正确的,因为eq()也是一个参数匹配器
verify(mock).someMethod(anyInt(), anyString(), "third argument");
// 上述代码是错误的,因为所有参数必须由匹配器提供,而参数"third argument"并非由参数匹配器提供,因此会抛出异常
像anyObject(), eq()这样的匹配器函数不会返回匹配器。它们会在内部将匹配器记录到一个栈当中,并且返回一个假的值,通常为null。这样的实现是由于被Java编译器强加的静态类型安全检查
。结果就是你不能在验证或者测试桩函数之外使用anyObject(), eq()函数。
4. 验证函数的确切、最少、从未调用的次数
//使用mock对象
mockedList.add("once");
mockedList.add("twice");
mockedList.add("twice");
mockedList.add("three times");
mockedList.add("three times");
mockedList.add("three times");
// 下面的两个验证函数效果一样,因为verify默认验证的就是times(1)
verify(mockedList).add("once");
verify(mockedList, times(1)).add("once");
// 验证具体的执行次数
verify(mockedList, times(2)).add("twice");
verify(mockedList, times(3)).add("three times");
// 使用never()进行验证,never相当于times(0)
verify(mockedList, never()).add("never happened");
// 使用atLeast()/atMost()进行验证
verify(mockedList, atLeastOnce()).add("three times");
verify(mockedList, atLeast(2)).add("five times");
verify(mockedList, atMost(5)).add("three times");
verify函数默认验证的是执行了times(1),也就是某个测试函数是否执行了1次.因此,times(1)通常被省略了。
5. 通过打桩为无返回值函数抛出异常
doThrow(new RuntimeException()).when(mockedList).clear();
// 调用这句代码会抛出运行时异常
mockedList.clear();
关于doThrow|doAnswer 等函数族的信息请阅读第十二章节。
6. 验证执行执行顺序
// A. 一个方法必须以指定顺序执行的模拟的对象
List singleMock = mock(List.class);
//使用这个模拟的对象
singleMock.add("was added first");
singleMock.add("was added second");
// 为该mock对象创建一个inOrder验证器
InOrder inOrder = inOrder(singleMock);
// 确保add函数首先执行的是add("was added first"),然后才是add("was added second")
inOrder.verify(singleMock).add("was added first");
inOrder.verify(singleMock).add("was added second");
// B .验证多个mock对象的必须以指定顺序被调用
List firstMock = mock(List.class);
List secondMock = mock(List.class);
//使用多个模拟的对象
firstMock.add("was called first");
secondMock.add("was called second");
// 创建inOrder对象,将需要验证的模拟对象传进来
InOrder inOrder = inOrder(firstMock, secondMock);
// 下面将会确认第一个模拟对象会在第二个模拟对象前被调用
inOrder.verify(firstMock).add("was called first");
inOrder.verify(secondMock).add("was called second");
// Oh, and A + B can be mixed together at will
验证执行顺序是非常灵活的 - 你不需要一个一个的验证所有交互,只需要验证你感兴趣的对象即可。
另外,你可以仅通过那些需要验证顺序的mock对象来创建InOrder对象。
7. 确认交互(interaction)操作没有在mock对象上执行
// 使用Mock对象 - 只有 mockOne发生了交互
mockOne.add("one");
// 普通验证
verify(mockOne).add("one");
// 验证一个模拟对象没有执行某个方法
verify(mockOne, never()).add("two");
// 验证其他mock对象没有交互
verifyZeroInteractions(mockTwo, mockThree);
8. 查找多余的调用
//using mocks
mockedList.add("one");
mockedList.add("two");
verify(mockedList).add("one");
// 下面的验证将会失败
verifyNoMoreInteractions(mockedList);
一些做了很多经典的、期望-执行-测试风格的模拟的用户,可能倾向于经常使用verifyNoMoreInteractions()
,甚至在每个测试函数中都用。但是verifyNoMoreInteractions()
并不建议在每个测试函数中都使用。verifyNoMoreInteractions()
在交互测试套件中只是一个便利的验证,它的作用是当你需要验证是否存在冗余调用时。滥用它将导致测试代码的可维护性降低。你可以阅读这篇文档来了解更多相关信息。
也可以看看never()
,它更加明确,并且很好地传达了意图。
9. 简化mock对象的创建 - @Mock注解
- 最小化重复的创建代码
- 使测试类的代码可读性更高
- 使验证错误更易于阅读,因为字段名可用于标识mock对象
public class ArticleManagerTest {
@Mock private ArticleCalculator calculator;
@Mock private ArticleDatabase database;
@Mock private UserProvider userProvider;
private ArticleManager manager;
注意! 下面这句代码需要在运行测试函数之前被调用,一般放到测试类的基类或者test runner中:
MockitoAnnotations.initMocks(testClass);
你可以使用内置的runner: MockitoJUnitRunner runner 或者一个rule : MockitoRule。关于JUnit5的测试案例,查看45章节关于JUnit扩展的描述。
关于mock注解的更多信息可以阅读MockitoAnnotations文档。
10. 为连续的调用打桩 (迭代器风格的打桩)
有时我们需要为同一个函数调用的不同的返回值/异常做测试桩。典型的运用案例就是对迭代器的模拟。
原始版本的Mockito并没有这个特性来促进模拟行为更简单,例如,可以使用Iterable或者简单的集合来替换迭代器。这些方法提供了更自然的方式,在一些场景中为连续的调用做测试桩会很有用。示例如下 :
when(mock.someMethod("some arg"))
.thenThrow(new RuntimeException())
.thenReturn("foo");
// 第一次调用 : 抛出运行时异常
mock.someMethod("some arg");
// 第二次调用 : 输出"foo"
System.out.println(mock.someMethod("some arg"));
// 后续调用 : 也是输出"foo"
System.out.println(mock.someMethod("some arg"));
另外,连续调用的另一种更简短的版本 :
// 第一次调用时返回"one",第二次返回"two",第三次返回"three"
when(mock.someMethod("some arg"))
.thenReturn("one", "two", "three");
注意:如果替代了“.thenReturn()"的调用,多个使用相同匹配器或参数的打桩动作,那么每个打桩动作都会覆盖之前的动作:
//All mock.someMethod("some arg") calls will return "two"
when(mock.someMethod("some arg"))
.thenReturn("one");
when(mock.someMethod("some arg"))
.thenReturn("two");
11. 通过回调方式来打桩
允许通过泛型Answer接口进行打桩。
在最初的Mockito里也没有这个具有争议性的特性。我们建议使用thenReturn() 或thenThrow()来打桩。这两种方法足够用于测试或者测试驱动开发。
如果你需要用泛型Answer接口来打桩,下面是个例子:
when(mock.someMethod(anyString())).thenAnswer(new Answer() {
Object answer(InvocationOnMock invocation) {
Object[] args = invocation.getArguments();
Object mock = invocation.getMock();
return "called with arguments: " + args;
}
});
// 输出 : "called with arguments: foo"
System.out.println(mock.someMethod("foo"));
12. doReturn()|doThrow()| doAnswer()|doNothing()|doCallRealMethod()系列方法
通过when(Object)
为无返回值的函数打桩有不同的方法,因为编译器不喜欢void函数在括号内…
当你想为void函数打桩来跑出一个异常时,使用doThrow()方法:
doThrow(new RuntimeException()).when(mockedList).clear();
// 下面的代码会抛出运行时异常
mockedList.clear();
当你调用doThrow()
, doAnswer()
, doNothing()
, doReturn()
and doCallRealMethod()
这些函数时,可以在适当的位置调用when()
函数. 当你需要下面这些功能时这是必须的:
- 测试void函数
- 在受监控的对象上测试函数
- 同一个函数多次打桩,为了在测试过程中改变mock对象的行为。
但是在调用when()
函数时你可以选择是否调用这些上述这些函数。
阅读更多关于这些方法的信息: