Mockito是基于CGLIB代理,实现打桩。它通过拦截对象的所有操作方法,对于满足打桩条件的调用,返回预设的返回值。
主要注解
@InjectMocks
用于标记对象属性允许用mock或spy注入。尝试通过按「先构造函数注入再setter注入最后属性(字段)注入」的顺序注入依赖。
-
构造函数注入:选取最大的构造函数,用已声明的mock作为参数注入;注:如果已经通过构造注入,将不再尝试其他策略注入(即不会再有如下两种的注入);
-
setter注入:先通过类型匹配,如果匹配的类型有多个mock实例,再通过名字匹配;
-
字段注入:同setter注入(不同的是字段可以不提供setter方法),先类型再名字;
@Mock
用于标记一个Mock字段,被标记的对象将被创建为mock对象(对于没有打桩的方法,返回值为默认值0或null或false);
- 允许多次代理mock对象的同一个方法,但具体的行为取决于该方法最近的一次代理行为
- mock对象的代理方法,允许多次调用,只要不覆盖它的代理行为,那么每次调用的执行相同的行为或者返回相同的值
- 相同的方法和参数唯一确认一个代理。比如你可以分别代理get(int)方法在参数分别为0和1时的不同行为
@MockBean
可以在Spring ApplicationContext容器内Mock Bean。做了@MockBean标注的Mock对象会通过类型或名字(若注解中指定名字)注入到Spring容器中。容器中定义的任何类型的单例的bean将会被mock替代(若容器中不存在,则会新建)。
@Spy
与@Mock不同的是,@Spy标注的mock对象是基于真实的实例创建,对mock对象的方法调用同时也会调用真实对象的方法;
@Test
public void whenSpyingOnList_thenCorrect() {
List<String> list = new ArrayList<String>();
List<String> spyList = Mockito.spy(list);
spyList.add("one");
spyList.add("two");
Mockito.verify(spyList).add("one");
Mockito.verify(spyList).add("two");
assertEquals(2, spyList.size());
}
可以像使用mock对象时的语法一样,配置或者说重新改写spy对象的方法,下面的例子使用了doReturn()来重写了size()方法:
@Test
public void whenStubASpy_thenStubbed() {
List<String> list = new ArrayList<String>();
List<String> spyList = Mockito.spy(list);
assertEquals(0, spyList.size());
Mockito.doReturn(100).when(spyList).size();
assertEquals(100, spyList.size());
}
注:
-
在监控对象上使用when(Object)来进行打桩有时候是不可能或者不切实际的。
在使用spy的时候,推荐用 doReturn|Answer|Throw() 等一系列的方法来打桩。
List list = new LinkedList(); List spy = spy(list); // 不可能: // 因为当调用spy.get(0)时会调用真实对象的get(0)函数, // 此时会发生IndexOutOfBoundsException,因为真实list对象是空的。 when(spy.get(0)).thenReturn("foo"); // 你需要使用 doReturn来进行打桩。 doReturn("foo").when(spy).get(0);
-
另外注意, Mockito并不会为真实对象代理函数调用,实际上它会复制真实对象。
所以,如果你保留了真实对象并与其交互,不要期望从监控对象得到正确的结果。
当你在监控对象上调用一个没有被打桩的函数时,并不会调用真实对象的对应函数,因此你不会在真实对象上看到任何结果。 -
Mock vs. Spy
当Mockito创造一个mock对象时,它是从类的类型中创建,而不是从类的实例中创建。也就是说mock对象是一个简单的只带有骨架的空壳的对象实例,同时我们可以跟踪它的所有交互方法。不同的是,spy对象是对一个对象实例进行的封装,它仍然能够像正常对象实例一样有这正常的行为,唯一不同的是这些行为交互是可以跟踪和配置的。
@Test public void whenCreateMock_thenCreated() { List mockedList = Mockito.mock(ArrayList.class); mockedList.add("one"); Mockito.verify(mockedList).add("one"); assertEquals(0, mockedList.size()); } @Test public void whenCreateSpy_thenCreate() { List spyList = Mockito.spy(new ArrayList()); spyList.add("one"); Mockito.verify(spyList).add("one"); assertEquals(1, spyList.size()); }
用法指南
-
基本用法
Mockito.when(list.size()).thenReturn(1);
上述代码表示,当对list对象调用size()方法时,会返回1.这样我们就可以自定义mock对象的行为了。
-
迭代风格的返回值设定
// 第一种方式 when(i.next()).thenReturn("Hello").thenReturn("World"); // 第二种方式 when(i.next()).thenReturn("Hello", "World"); // 第三种方式 when(i.next()).thenReturn("Hello"); when(i.next()).thenReturn("World");
-
没有返回值的打桩
Mockito.doNothing().when(obj).notify(); // 或直接 when(obj).notify();
-
对被测试的方法强行抛出异常
when(i.next()).thenThrow(new RuntimeException()); // void 方法的 doThrow(new RuntimeException()).when(i).remove();
-
模拟传入的参数 (Mockito.anyInt())
when(mockedList.get(anyInt())).thenReturn("element"); System.out.println(mockedList.get(999)); // 此时打印是 element
anyString() 匹配任何 String 参数,
anyInt() 匹配任何 int 参数,
anySet() 匹配任何 Set,
any() 则意味着参数为任意值
再进一步,自定义类型也可以,如 any(User.class)
自定义参数匹配器 可以实现 ArgumentMatcher<?> 接口,
然后 when(mock.addAll(argThat(new YourImplClass()))).thenReturn(true) -
获取返回的结果: 实现 Answer<?>接口
final Map<String, Object> hash = new HashMap<String, Object>(); Answer<String> answer = new Answer<String>() { public String answer(InvocationOnMock invocation) { Object[] args = invocation.getArguments(); return hash.get(args[0].toString()).toString(); } }; when(request.getAttribute("errMsg")).thenAnswer(answer);
利用 InvocationOnMock 提供的方法可以获取 mock 方法的调用信息。下面是它提供的方法:
-
getArguments() 调用后会以 Object 数组的方式返回 mock 方法调用的参数。
-
getMethod() 返回 java.lang.reflect.Method 对象
-
getMock() 返回 mock 对象
-
callRealMethod() 真实方法调用,如果 mock 的是接口它将会抛出异常
void 方法可以获取参数,只是写法上有区别,
Answer<String> answer = new Answer<Object>() { public String answer(InvocationOnMock invocation) { Object[] args = invocation.getArguments(); System.out.println(args[1]); hash.put(args[0].toString(), args[1]); return "called with arguments: " + args; } }; doAnswer(answer).when(request).setAttribute(anyString(), anyString());
-
验证方法
前面提到的 when(……).thenReturn(……) 属于状态测试,
某些时候,测试不关心返回结果,而是侧重方法有否被正确的参数调用过,这时候就应该使用 验证方法了。
从概念上讲,就是和状态测试所不同的“行为测试”了。
一旦使用 mock() 对模拟对象打桩,意味着 Mockito 会记录着这个模拟对象调用了什么方法,还有调用了多少次。
最后由用户决定是否需要进行验证,即 verify() 方法。mockedList.add("one"); mockedList.add("two"); // 如果times不传入,则默认是1, 表示验证 mockedList 是否被调用了 add("one")方法1次。 verify(mockedList,times(1)).add("one"); Map mock = Mockito.mock( Map.class ); when(mock.get("city")).thenReturn("广州"); // 关注参数有否传入 verify(mock).get(Matchers.eq("city")); // verify 也可以使用模拟参数,但是若方法中的某一个参数使用了matcher,则所有的参数都必须使用 matcher。 // 例如 下面的语句会抛出异常, verify(mock).someMethod(anyInt(), anyString(), "third argument"); // 关注调用的次数 verify(mock, times(2)).get(Matchers.eq("city")); //还有 never()==times(0)、atLeast(N)、atLeastOnce()===atLeast(1)、atMost(N) //验证mock对象是否存在没有验证过的调用方法 verifyNoMoreInteractions(mock) ; //验证mock对象是否没有进行任何交互 verifyZeroInteractions(mock); //超时验证 单位毫秒 verify(mock, timeout(100)).someMethod(); //在给定的时间内完成执行次数 verify(mock, timeout(100).times(2)).someMethod(); //给定的时间内至少执行两次 verify(mock, timeout(100).atLeast(2)).someMethod(); //还有 验证方法调用的顺序 inOrder,不再详述。 //还有 验证参数交互时所传入方法的参数,使用 ArgumentCaptor argument = ArgumentCaptor.forClass(String.class); 不再详述
-
参数捕捉
@Test public void testCapturingArguments() throws Exception { List mockedList = mock(List.class); ArgumentCaptor<String> argument = ArgumentCaptor.forClass(String.class); mockedList.add("John"); //验证后再捕捉参数 verify(mockedList).add(argument.capture()); //验证参数 assertEquals("John", argument.getValue()); }
注:可用 @Captor 创建ArgumentCaptor
-
部分Mock
//用spy()方法创建部分mock List list = spy(new LinkedList()); //用thenCallRealMethod方法 when(mock.someMethod()).thenCallRealMethod();
原理浅析
-
标记为InjectMocks的对象注入的mock对象来自哪里?
标记为InjectMocks的字段注入的依赖mock为同一testInstance(一个测试类为一个testInstance)下的其他mock字段。
通过调用方法 MockitoAnnotations.initMocks(Object testInstance) 实现Mock注入:先分类无依赖mock和有依赖mock;然后先创建无依赖的mock实例,再将创建好的无依赖mock对象集作为参数创建依有依赖mock实例。
进阶——PowerMock
Mockito可以实现常规的一些测试需求,如果有很难甚至无法测试的测试问题,可以考虑PowerMock。PowerMock可以模拟静态方法,删除静态初始化程序,允许模拟而不依赖于注入,等等。PowerMock通过在执行测试时在运行时修改字节码来完成这些技巧。PowerMock还包含一些实用程序,可以更轻松地访问对象的内部状态。
参考
mockito wiki
Mockito 源码解析
Mockito Spy 用法
Mockito测试
Mockito使用指南