Mockito 简明教程

原文同步至 http://waylau.com/mockito-quick-start/html

Mock 测试是单元测试的重要方法之一。本文介绍了基于 Java 语言的 Mock 测试框架 -- Mockito 的使用。java

什么是 Mock 测试

Mock 测试就是在测试过程当中,对于某些不容易构造(如 HttpServletRequest 必须在Servlet 容器中才能构造出来)或者不容易获取比较复杂的对象(如 JDBC 中的ResultSet 对象),用一个虚拟的对象(Mock 对象)来建立以便测试的测试方法。git

Mock 最大的功能是帮你把单元测试的耦合分解开,若是你的代码对另外一个类或者接口有依赖,它可以帮你模拟这些依赖,并帮你验证所调用的依赖的行为。github

好比一段代码有这样的依赖:app

当咱们须要测试A类的时候,若是没有 Mock,则咱们须要把整个依赖树都构建出来,而使用 Mock 的话就能够将结构分解开,像下面这样:框架

Mock 对象使用范畴

真实对象具备不可肯定的行为,产生不可预测的效果,(如:股票行情,天气预报) 真实对象很难被建立的 真实对象的某些行为很难被触发 真实对象实际上还不存在的(和其余开发小组或者和新的硬件打交道)等等maven

使用 Mock 对象测试的关键步骤

使用一个接口来描述这个对象 在产品代码中实现这个接口 在测试代码中实现这个接口 在被测试代码中只是经过接口来引用对象,因此它不知道这个引用的对象是真实对象,仍是 Mock 对象。ide

Java Mock 测试

目前,在 Java 阵营中主要的 Mock 测试工具备 MockitoJMockEasyMock 等。svn

关于这些框架的比较,不是本文的重点。本文着重介绍 Mockito 的使用。工具

Mockito 的特性

Mockito 是美味的 Java 单元测试 Mock 框架,开源

大多 Java Mock 库如 EasyMock 或 JMock 都是 expect-run-verify (指望-运行-验证)方式,而 Mockito 则使用更简单,更直观的方法:在执行后的互动中提问。使用 Mockito,你能够验证任何你想要的。而那些使用 expect-run-verify 方式的库,你经常被迫查看无关的交互。

非 expect-run-verify 方式 也意味着,Mockito 无需准备昂贵的前期启动。他们的目标是透明的,让开发人员专一于测试选定的行为。

Mockito 拥有的很是少的 API,全部开始使用 Mockito,几乎没有时间成本。由于只有一种创造 mock 的方式。只要记住,在执行前 stub,然后在交互中验证。你很快就会发现这样 TDD java 代码是多么天然。

相似 EasyMock 的语法来的,因此你能够放心地重构。Mockito 并不须要“expectation(指望)”的概念。只有 stub 和验证。

Mockito 实现了 Gerard Meszaros 所谓的 Test Spy.

其余的一些特色:

  • 能够 mock 具体类而不单止是接口
  • 一点注解语法糖 - @Mock
  • 干净的验证错误是 - 点击堆栈跟踪,看看在测试中的失败验证;点击异常的缘由来导航到代码中的实际互动。堆栈跟踪老是干干净净。
  • 容许灵活有序的验证(例如:你任意有序 verify,而不是每个单独的交互)
  • 支持“详细的用户号码的时间”以及“至少一​​次”验证
  • 灵活的验证或使用参数匹配器的 stub (anyObject()anyString()refEq() 用于基于反射的相等匹配)
  • 容许建立自定义的参数匹配器或者使用现有的 hamcrest 匹配器

Mockito 入门

声明 mockito 依赖

Gradle 用户可使用:

repositories { jcenter() }
dependencies { testCompile "org.mockito:mockito-core:1.+" }

Maven 用户可使用:http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.mockito%22%2C%20a%3A%22mockito-core%22

Mockito 自动发布到 http://jcenter.bintray.com/org/mockito/mockito-core/ 并同步到 Maven Central Repository

示例

1.验证行为

//Let's import Mockito statically so that the code looks clearer
 import static org.mockito.Mockito.*;

 //mock creation
 List mockedList = mock(List.class);

 //using mock object
 mockedList.add("one");
 mockedList.clear();

 //verification
 verify(mockedList).add("one");
 verify(mockedList).clear();

一旦建立 mock 将会记得全部的交互。你能够选择验证你感兴趣的任何交互

2.stubbing

//You can mock concrete classes, not just interfaces
 LinkedList mockedList = mock(LinkedList.class);

 //stubbing
 when(mockedList.get(0)).thenReturn("first");
 when(mockedList.get(1)).thenThrow(new RuntimeException());

 //following prints "first"
 System.out.println(mockedList.get(0));

 //following throws runtime exception
 System.out.println(mockedList.get(1));

 //following prints "null" because get(999) was not stubbed
 System.out.println(mockedList.get(999));

 //Although it is possible to verify a stubbed invocation, usually it's just redundant
 //If your code cares what get(0) returns, then something else breaks (often even before verify() gets executed).
 //If your code doesn't care what get(0) returns, then it should not be stubbed. Not convinced? See here.
 verify(mockedList).get(0);
  • 默认状况下,全部方法都会返回值,一个 mock 将返回要么 null,一个原始/基本类型的包装值或适当的空集。例如,对于一个 int/Integer 就是 0,而对于 boolean/Boolean 就是 false。
  • Stubbing 能够被覆盖。
  • 一旦 stub,该方法将始终返回一个 stub 的值,不管它有多少次被调用。
  • 最后的 stubbing 是很重要的 - 当你使用相同的参数 stub 屡次一样的方法。换句话说:stubbing 的顺序是重要的,但它惟一有意义的却不多,例如当 stubbing 彻底相同的方法调用,或者有时当参数匹配器的使用,等等。

3.参数匹配器

Mockito 验证参数值使用 Java 方式:经过使用 equals() 方法。有时,当须要额外的灵活性,可使用参数匹配器:

//stubbing using built-in anyInt() argument matcher
 when(mockedList.get(anyInt())).thenReturn("element");

 //stubbing using custom matcher (let's say isValid() returns your own matcher implementation):
 when(mockedList.contains(argThat(isValid()))).thenReturn("element");

 //following prints "element"
 System.out.println(mockedList.get(999));

 //you can also verify using an argument matcher
 verify(mockedList).get(anyInt());

参数匹配器容许灵活的验证或 stubbing。点击这里查看更多内置的匹配器和自定义的参数匹配器/ hamcrest匹配器的例子。

自定义参数的匹配信息,请查看 Javadoc 中 ArgumentMatcher 类。

若是你正在使用参数的匹配,全部的参数都由匹配器来提供。

下面的示例演示验证,但一样适用于 stubbing:

verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
//above is correct - eq() is also an argument matcher

verify(mock).someMethod(anyInt(), anyString(), "third argument");
//above is incorrect - exception will be thrown because third argument is given without an argument matcher.

4.调用额外的调用数字/at least x / never

//using mock
mockedList.add("once");

mockedList.add("twice");
mockedList.add("twice");

mockedList.add("three times");
mockedList.add("three times");
mockedList.add("three times");

//following two verifications work exactly the same - times(1) is used by default
verify(mockedList).add("once");
verify(mockedList, times(1)).add("once");

//exact number of invocations verification
verify(mockedList, times(2)).add("twice");
verify(mockedList, times(3)).add("three times");

//verification using never(). never() is an alias to times(0)
verify(mockedList, never()).add("never happened");

//verification using atLeast()/atMost()
verify(mockedList, atLeastOnce()).add("three times");
verify(mockedList, atLeast(2)).add("five times");
verify(mockedList, atMost(5)).add("three times");

times(1) 是默认的,所以,使用的 times(1) 能够显示的省略。

5.Stubbing void 方法处理异常

doThrow(new RuntimeException()).when(mockedList).clear();

//following throws RuntimeException:
mockedList.clear();

6.有序的验证

// A. Single mock whose methods must be invoked in a particular order
List singleMock = mock(List.class);

//using a single mock
singleMock.add("was added first");
singleMock.add("was added second");

//create an inOrder verifier for a single mock
InOrder inOrder = inOrder(singleMock);

//following will make sure that add is first called with "was added first, then with "was added second"
inOrder.verify(singleMock).add("was added first");
inOrder.verify(singleMock).add("was added second");

// B. Multiple mocks that must be used in a particular order
List firstMock = mock(List.class);
List secondMock = mock(List.class);

//using mocks
firstMock.add("was called first");
secondMock.add("was called second");

//create inOrder object passing any mocks that need to be verified in order
InOrder inOrder = inOrder(firstMock, secondMock);

//following will make sure that firstMock was called before 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

有序验证是为了灵活 - 你没必要一个接一个验证全部的交互。

此外,您还能够经过建立 InOrder 对象传递只与有序验证相关的 mock 。

7. 确保 mock 上不会发生交互

//using mocks - only mockOne is interacted
mockOne.add("one");

//ordinary verification
verify(mockOne).add("one");

//verify that method was never called on a mock
verify(mockOne, never()).add("two");

//verify that other mocks were not interacted
verifyZeroInteractions(mockTwo, mockThree);

8.寻找多余的调用

//using mocks
mockedList.add("one");
mockedList.add("two");

verify(mockedList).add("one");

//following verification will fail
verifyNoMoreInteractions(mockedList);

注意:不建议 verifyNoMoreInteractions() 在每一个测试方法中使用。 verifyNoMoreInteractions() 是从交互测试工具包一个方便的断言。只有与它的相关时才使用它。滥用它致使难以维护。

9. 标准建立 mock 方式 - 使用 @Mock 注解

  • 最小化可重用 mock 建立代码

  • 使测试类更加可读性

  • 使验证错误更加易读,由于字段名称用于惟一识别 mock

    public class ArticleManagerTest {

    @Mock private ArticleCalculator calculator;
     @Mock private ArticleDatabase database;
     @Mock private UserProvider userProvider;
    
     private ArticleManager manager;

在基础类或者测试 runner 里面,使用以下:

MockitoAnnotations.initMocks(testClass);

可使用内建 runner: MockitoJUnitRunner 或者 rule: MockitoRule

更多详见 MockitoAnnotations

10. Stubbing 连续调用(迭代器式的 stubbing)

when(mock.someMethod("some arg"))
 .thenThrow(new RuntimeException())
 .thenReturn("foo");

//First call: throws runtime exception:
mock.someMethod("some arg");

//Second call: prints "foo"
System.out.println(mock.someMethod("some arg"));

//Any consecutive call: prints "foo" as well (last stubbing wins).
System.out.println(mock.someMethod("some arg"));

下面是一个精简版本:

when(mock.someMethod("some arg"))
 .thenReturn("one", "two", "three");

11. 回调 Stubbing

容许使用泛型 Answer 接口。

然而,这是不包括在最初的 Mockito 另外一个有争议的功能。咱们建议您只需用thenReturn() 或 thenThrow() 来 stubbing ,这在测试/测试驱动中应用简洁与简单的代码足够了。可是,若是你有一个须要 stub 到泛型 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;
   }
});

//the following prints "called with arguments: foo"
System.out.println(mock.someMethod("foo"));

12. doReturn()|doThrow()| doAnswer()|doNothing()|doCallRealMethod() 家族方法

Stubbing void 方法,须要不一样的 when(Object) ,由于编译器不喜欢括号内无效的方法...

在 用于 Stubbing void 方法中,doThrow(Throwable...) 取代 stubVoid(Object)。主要缘由是提升可读性和与 doAnswer() 保持一致性。

当你想用 stub void 方法 使用 doThrow():

doThrow(new RuntimeException()).when(mockedList).clear();

//following throws RuntimeException:
mockedList.clear();

在调用 when() 的相应地方可使用 oThrow(), doAnswer(), doNothing(), doReturn() 和 doCallRealMethod(),当:

  • stub void 方法
  • stub 方法在 spy 对象(见下面)
  • 能够不止一次的 stub 相同的方法,在测试的中期来改变 mock 的行为

但你更加倾向于使用这些方法来代替 when(),在全部的 stubbing 调用。能够阅读更多关于这些方法的描述:

doReturn(Object)

doThrow(Throwable...)

doThrow(Class)

doAnswer(Answer)

doNothing()

doCallRealMethod()

参考

相关文章
相关标签/搜索