赞
踩
Mockito
是当前最流行的 单元测试 Mock
框架。采用 Mock
框架,我们可以 虚拟 出一个 外部依赖,降低测试 组件 之间的 耦合度,只注重代码的 流程与结果,真正地实现测试目的。
Mock
的中文译为仿制的,模拟的,虚假的。对于测试框架来说,即构造出一个模拟/虚假的对象,使我们的测试能顺利进行下去。
Mock
测试就是在测试过程中,对于某些 不容易构造(如 HttpServletRequest
必须在 Servlet
容器中才能构造出来)或者不容易获取 比较复杂 的对象(如 JDBC
中的 ResultSet
对象),用一个 虚拟 的对象(Mock
对象)来创建,以便测试方法。
单元测试 是为了验证我们的代码运行正确性,我们注重的是代码的流程以及结果的正确与否。
对比真实运行代码,可能其中有一些 外部依赖 的构建步骤相对麻烦,如果我们还是按照真实代码的构建规则构造出外部依赖,会大大增加单元测试的工作,代码也会参杂太多非测试部分的内容,测试用例显得复杂难懂。
采用 Mock
框架,我们可以 虚拟 出一个 外部依赖,只注重代码的 流程与结果,真正地实现测试目的。
mock
对象的行为;如图所示,使用 Mockito
的大致流程如下:
Mock
对象, 然后将此 Mock
对象注入到 测试类 中;在 Module
的 build.gradle
中添加如下内容:
- dependencies {
- //Mockito for unit tests
- testImplementation "org.mockito:mockito-core:2.+"
- //Mockito for Android tests
- androidTestImplementation 'org.mockito:mockito-android:2.+'
- }
这里稍微解释下:
mockito-core
: 用于 本地单元测试,其测试代码路径位于 module-name/src/test/java/
mockito-android
: 用于 设备测试,即需要运行 android
设备进行测试,其测试代码路径位于 module-name/src/androidTest/java/
mockito-core最新版本可以在 Maven 中查询:mockito-core。 mockito-android最新版本可以在 Maven 中查询:mockito-android
普通单元测试使用 mockito(mockito-core)
,路径:module-name/src/test/java/
这里摘用官网的 Demo
:
- import static org.mockito.Mockito.*;
-
- // Mock creation
- List mockedList = mock(List.class);
-
- // Use mock object - it does not throw any "unexpected interaction" exception
- mockedList.add("one"); //调用了add("one")行为
- mockedList.clear(); //调用了clear()行为
-
- // Selective, explicit, highly readable verification
- verify(mockedList).add("one"); // 检验add("one")是否已被调用
- verify(mockedList).clear(); // 检验clear()是否已被调用
这里 mock
了一个 List
(这里只是为了用作 Demo
示例,通常对于 List
这种简单的类对象创建而言,直接 new
一个真实的对象即可,无需进行 mock
),verify()
会检验对象是否在前面已经执行了相关行为,这里 mockedList
在 verify
之前已经执行了 add("one")
和 clear()
行为,所以verify()
会通过。
- // you can mock concrete classes, not only interfaces
- LinkedList mockedList = mock(LinkedList.class);
- // stubbing appears before the actual execution
- when(mockedList.get(0)).thenReturn("first");
- // the following prints "first"
- System.out.println(mockedList.get(0));
- // the following prints "null" because get(999) was not stubbed
- System.out.println(mockedList.get(999));
这里对几个比较重要的点进行解析:
when(mockedList.get(0)).thenReturn("first")
这句话 Mockito
会解析为:当对象 mockedList
调用 get()
方法,并且参数为 0
时,返回结果为"first"
,这相当于定制了我们 mock
对象的行为结果(mock LinkedList
对象为 mockedList
,指定其行为 get(0)
,则返回结果为 "first"
)。
mockedList.get(999)
由于 mockedList
没有指定 get(999)
的行为,所以其结果为 null
。因为 Mockito
的底层原理是使用 cglib
动态生成一个 代理类对象,因此,mock
出来的对象其实质就是一个 代理,该代理在 没有配置/指定行为 的情况下,默认返回 空值。
上面的 Demo
使用的是 静态方法 mock()
模拟出一个实例,我们还可以通过注解 @Mock
也模拟出一个实例:
- @Mock
- private Intent mIntent;
-
- @Rule
- public MockitoRule mockitoRule = MockitoJUnit.rule();
-
- @Test
- public void mockAndroid(){
- Intent intent = mockIntent();
- assertThat(intent.getAction()).isEqualTo("com.yn.test.mockito");
- assertThat(intent.getStringExtra("Name")).isEqualTo("Whyn");
- }
-
- private Intent mockIntent(){
- when(mIntent.getAction()).thenReturn("com.yn.test.mockito");
- when(mIntent.getStringExtra("Name")).thenReturn("Whyn");
- return mIntent;
- }

对于标记有 @Mock
, @Spy
, @InjectMocks
等注解的成员变量的 初始化 到目前为止有 2
种方法:
JUnit
测试类添加 @RunWith(MockitoJUnitRunner.class)
@Before
方法内调用初始化方法:MockitoAnnotations.initMocks(Object)
上面的测试用例,对于 @Mock
等注解的成员变量的初始化又多了一种方式 MockitoRule
。规则 MockitoRule
会自动帮我们调用 MockitoAnnotations.initMocks(this)
去 实例化 出 注解 的成员变量,我们就无需手动进行初始化了。
- // 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
。stub
)是可以被复写的:比如通常的对象行为是具有一定的配置,但是测试方法可以复写这个行为。请谨记行为复写可能表明潜在的行为太多了。Mockito
通过参数对象的 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());
- // Argument matchers can also be written as Java 8 Lambdas
- verify(mockedList).add(argThat(someString -> someString.length() > 5));
参数匹配器 允许更加灵活的 验证 和 行为配置。更多 内置匹配器 和 自定义参数匹配器 例子请参考:ArgumentMatchers
,MockitoHamcrest
注意:如果使用了参数匹配器,那么所有的参数都需要提供一个参数匹配器。
- 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.
类似 anyObject()
,eq()
这类匹配器并不返回匹配数值。他们内部记录一个 匹配器堆栈 并返回一个空值(通常为 null
)。这个实现是为了匹配 java
编译器的 静态类型安全,这样做的后果就是你不能在 检验/配置方法 外使用 anyObject()
,eq()
等方法。
- LinkedList mockedList = mock(LinkedList.class);
- // Use mock
- mockedList.add("once");
- mockedList.add("twice");
- mockedList.add("twice");
- mockedList.add("three times");
- mockedList.add("three times");
- mockedList.add("three times");
-
- // Follow 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("three times");
- verify(mockedList, atMost(5)).add("three times");

校验次数方法常用的有如下几个:
|Method |Meaning| |:------|:-------| |times(n)| 次数为n,默认为1(times(1))| |never()| 次数为0,相当于times(0)| |atLeast(n)|最少n次| |atLeastOnce()| 最少一次| |atMost(n)| 最多n次 |
- doThrow(new RuntimeException()).when(mockedList).clear();
- // following throws RuntimeException
- mockedList.clear();
有时对于一些行为,有先后顺序之分,所以,当我们在校验时,就需要考虑这个行为的先后顺序:
- // A. Single mock whose methods must be invoked in a particular order
- List singleMock = mock(List.class);
- // Use 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);
- // Use 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");

对于同一个方法,如果我们想让其在 多次调用 中分别 返回不同 的数值,那么就可以使用存根连续调用:
- 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");
注意:存根连续调用要求必须使用链式调用,如果使用的是同个方法的多个存根配置,那么只有最后一个起作用(覆盖前面的存根配置)。
- // All mock.someMethod("some arg") calls will return "two"
- when(mock.someMethod("some arg").thenReturn("one")
- when(mock.someMethod("some arg").thenReturn("two")
对于 返回类型 为 void
的方法,存根要求使用另一种形式的 when(Object)
函数,因为编译器要求括号内不能存在 void
方法。
例如,存根一个返回类型为 void
的方法,要求调用时抛出一个异常:
- doThrow(new RuntimeException()).when(mockedList).clear();
- // Following throws RuntimeException:
- mockedList.clear();
前面使用的都是 mock
出来一个对象。这样,当 没有配置/存根 其具体行为的话,结果就会返回 空类型。而如果使用 特务对象(spy
),那么对于 没有存根 的行为,它会调用 原来对象 的方法。可以把 spy
想象成局部 mock
。
- List list = new LinkedList();
- List spy = spy(list);
-
- // Optionally, you can stub out some methods:
- when(spy.size()).thenReturn(100);
- // Use the spy calls *real* methods
- spy.add("one");
- spy.add("two");
-
- // Prints "one" - the first element of a list
- System.out.println(spy.get(0));
- // Size() method was stubbed - 100 is printed
- System.out.println(spy.size());
- // Optionally, you can verify
- verify(spy).add("one");
- verify(spy).add("two");

注意:由于 spy 是局部 mock,所以有时候使用 when(Object) 时,无法做到存根作用。此时,就可以考虑使用 doReturn() | Answer() | Throw() 这类方法进行存根:
- List list = new LinkedList();
- List spy = spy(list);
- // Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty)
- when(spy.get(0)).thenReturn("foo");
- // You have to use doReturn() for stubbing
- doReturn("foo").when(spy).get(0);
spy
并不是 真实对象 的 代理。相反的,它对传递过来的 真实对象 进行 克隆。所以,对 真实对象 的任何操作,spy
对象并不会感知到。同理,对 spy
对象的任何操作,也不会影响到 真实对象。
当然,如果使用 mock
进行对象的 局部 mock
,通过 doCallRealMethod() | thenCallRealMethod()
方法也是可以的:
- // You can enable partial mock capabilities selectively on mocks:
- Foo mock = mock(Foo.class);
- // Be sure the real implementation is 'safe'.
- // If real implementation throws exceptions or depends on specific state of the object then you're in trouble.
- when(mock.someMethod()).thenCallRealMethod();
以 行为驱动开发 的格式使用 //given //when //then 注释为测试用法基石编写测试用例,这正是 Mockito
官方编写测试用例方法,强烈建议使用这种方式测试编写。
- import static org.mockito.BDDMockito.*;
-
- Seller seller = mock(Seller.class);
- Shop shop = new Shop(seller);
-
- public void shouldBuyBread() throws Exception {
- // Given
- given(seller.askForBread()).willReturn(new Bread());
- // When
- Goods goods = shop.buyBread();
- // Then
- assertThat(goods, containBread());
- }
- // Will print a custom message on verification failure
- verify(mock, description("This will print on failure")).someMethod();
- // Will work with any verification mode
- verify(mock, times(2).description("someMethod should be called twice")).someMethod();
构造器,方法,成员变量依赖注入 使用 @InjectMock
注解时,Mockito
会检查 类构造器,方法或 成员变量,依据它们的 类型 进行自动 mock
。
- public class InjectMockTest {
- @Mock
- private User user;
- @Mock
- private ArticleDatabase database;
- @InjectMocks
- private ArticleManager manager;
- @Rule
- public MockitoRule mockitoRule = MockitoJUnit.rule();
-
- @Test
- public void testInjectMock() {
- // Calls addListener with an instance of ArticleListener
- manager.initialize();
- // Validate that addListener was called
- verify(database).addListener(any(ArticleListener.class));
- }
-
- public static class ArticleManager {
- private User user;
- private ArticleDatabase database;
-
- public ArticleManager(User user, ArticleDatabase database) {
- super();
- this.user = user;
- this.database = database;
- }
-
- public void initialize() {
- database.addListener(new ArticleListener());
- }
- }
-
- public static class User {
- }
-
- public static class ArticleListener {
- }
-
- public static class ArticleDatabase {
- public void addListener(ArticleListener listener) {
- }
- }
- }

成员变量 manager
类型为 ArticleManager
,它的上面标识别了 @InjectMocks
。这意味着要 mock
出 manager
,Mockito
需要先自动 mock
出 ArticleManager
所需的 构造参数(即:user
和 database
),最终 mock
得到一个 ArticleManager
,赋值给 manager
。
ArgumentCaptor
允许在 verify
的时候获取 方法参数内容,这使得我们能在 测试过程 中能对 调用方法参数 进行 捕捉 并 测试。
- @Rule
- public MockitoRule mockitoRule = MockitoJUnit.rule();
- @Captor
- private ArgumentCaptor<List<String>> captor;
- @Test
- public void testArgumentCaptor(){
- List<String> asList = Arrays.asList("someElement_test", "someElement");
- final List<String> mockedList = mock(List.class);
- mockedList.addAll(asList);
-
- verify(mockedList).addAll(captor.capture()); // When verify,you can capture the arguments of the calling method
- final List<String> capturedArgument = captor.getValue();
- assertThat(capturedArgument, hasItem("someElement"));
- }
mock
静态方法;mock
构造器;mock
equals()
和 hashCode()
方法。最后感谢每一个认真阅读我文章的人,看着粉丝一路的上涨和关注,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走
这些资料,对于从事【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!凡事要趁早,特别是技术行业,一定要提升技术功底。希望对大家有所帮助…….有需要的小伙伴可以点击下方小卡片免费领取
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。