Mockito
1 Overview
2 Maven 项目初始化
3 示例
3.1 第一个示例
3.2 自动 Mock
3.3 Mock 返回值
3.4 Mock 参数
3.5 自动注入 Mock 对象
3.6 验证调用次数
3.7 预设 Exception
3.8 Void Mock
3.9 级联 Mock
3.10 部分 Mock
4 FAQ
4.1 注意点
5 References
1 Overview
Mockito 是 Java 中用于 Mock 的一个开源项目。
Mock 用于如下目的
- 如果依赖的外部系统在测试环境下不可用,使用 Mock 跳过外部系统调用并模拟返回结果
- 验证是否的确调用了 Mock 对象
2 Maven 项目初始化
-
创建 Maven Quick Start 项目
-
pom.xml 修改如下
3 示例
3.1 第一个示例
我们先看如下代码
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import java.util.List; import org.junit.Test; public class FirstTest { @Test public void verifyBehavior() { @SuppressWarnings("unchecked") List<Integer> mock = mock(List.class); mock.add(1); mock.clear(); verify(mock).add(1); verify(mock).clear(); } }
作为第一个示例,我在此详细解释一下
首先,我们使用 Mockito.mock() 方来来生成 Mock 对象。这里我们需要注意两点:
- 可以直接生成接口的 Mock 对象
- 范型对象 Mock
然后我们调用了 Mock 对象的两个成员方法,在此我们需要更深刻地理解一下 Mock 对象的行为,如果我们在调用 add 方法后打印它的 size,我们会发现结果是 0 而不是 1。也就是说,Mock 对象只是拦截了所有对原始方法的调用并返回对应返回的类型的默认值,而不是真正地实现了这个接口或创建了对象实例。
然后是两个 verify 方法,表示验证 Mock 对象是否调用了对应的方法。注意验证 add 调用时,可以验证输入参数(本例为 1),如果不想验证,只需要确定是否调用,可以使用如下方式验证
import static org.mockito.Matchers.anyInt; ... verify(mock).add(anyInt());
3.2 自动 Mock
可以使用如下方式自动生成 Mock 对象
import org.junit.Before; import org.mockito.Mock; import org.mockito.MockitoAnnotations; ... @Mock private List<Integer> mock; @Before public void setup() { MockitoAnnotations.initMocks(this); }
3.3 Mock 返回值
下例 mock一个Iterator类,预设当iterator调用next()时第一次返回hello,以后每次都返回world
import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import java.util.Iterator; import org.junit.Test; public class MockReturnTest { @Test public void verifyBehavior1() { @SuppressWarnings("unchecked") Iterator<String> iterator = mock(Iterator.class); when(iterator.next()).thenReturn("hello").thenReturn("world"); String result = iterator.next() + " " + iterator.next() + " " + iterator.next(); assertEquals("hello world world", result); } }
3.4 Mock 参数
可以使用以下 Mockito 对象来模拟任意输入值
import static org.mockito.Matchers.any*
例如 anyString, anyInteger, anyChar 等,也可以使用 any() 方法来生成任意对象,例如
List<String> mock = mock(List.class); mock.add(any(String.class));
或者使用更简单的 Mockito.any(), 如下所示
import static org.mockito.Mockito.any; List<String> mock = mock(List.class); mock.add(any());
3.5 自动注入 Mock 对象
对于如下的情况,我们需要 Mock 某对象的内部方法,如下所示,我们需要 Mock MainServer 内部的 OtherService:
常规情况下,我们需要手工注入 Mock 对象,如下所示:
PS:需要在 MainService 类中添加 setOtherService() 方法以允许修改 otherService
其中 DemoConfig 是配置类,对于 Spring Boot 框架,Test 类不会自动注入 Autowired 对象,需要使用 Config 类指定加载类,内容如下:
但更合理的方式是使用 @InjectMocks 注解来自动注入,如下所示
注意如下几点:
- 在 @Before 方法中初始化 Mock 对象及自动注入
- 在需要自动注入成员的类上添加 @InjectMocks 注解
另外值得注意的是,@InjectMocks 只会注入当前对象的成员,不会递归深度注入对象,例如,我们如果将 MainService 修改如下:
添加 MiddleService 如下所示
这样的话,Unit Test 需要修改如下:
3.6 验证调用次数
如下代码验证了 add 方法需要被调用两次
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import java.util.List; import org.junit.Test; public class CallTimesTest { @Test public void verifyBehavior1() { List<Integer> mock = mock(List.class); mock.add(1); mock.add(1); verify(mock, times(2)).add(1); } }
3.7 预设 Exception
下面代码演示了预设 Exceptio 发生
import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import java.io.IOException; import java.io.OutputStream; import org.junit.Test; public class ExceptionTest { @Test(expected = IOException.class) public void when_thenThrow() throws IOException { OutputStream outputStream = mock(OutputStream.class); doThrow(new IOException()).when(outputStream).close(); outputStream.close(); } }
代码说明如下:
- @Test(expected = IOException.class) 表示该测试需要有 IOException 抛出
- doThrow 表示指定操作将抛出指定异常
3.8 Void Mock
前面的 thenReturn 只适用于有返回值的方法,本例讲述如何 Mock void 方法
声明服务类如下
package com.lld.test.mockito; import org.springframework.stereotype.Component; @Component public class VoidService { public void sayHi(String name) { System.out.println("Hello, " + name); } }
测试类如下所示
package com.lld.test.mockito; import static org.junit.Assert.assertEquals; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = DemoConfig.class) public class VoidTest { @Mock VoidService voidService; @Before public void setup() throws Exception { MockitoAnnotations.initMocks(this); } @Test public void voidTest() { doNothing().when(voidService).sayHi(anyString()); voidService.sayHi("Lindong"); verify(voidService, times(1)).sayHi(anyString()); } @Test public void voidArgumentTest() { ArgumentCaptor<String> argumentCaptor = ArgumentCaptor.forClass(String.class); doNothing().when(voidService).sayHi(argumentCaptor.capture()); voidService.sayHi("Lindong"); assertEquals("Lindong", argumentCaptor.getValue()); } @Test public void answerTest() { doAnswer(answer -> { String name = answer.getArgumentAt(0, String.class); System.out.println("invoke VoidService with argument: " + name); return null; }).when(voidService).sayHi(anyString()); voidService.sayHi("Lindong"); } }
代码说明如下
- voidTest 演示了如何简单地 Mock void 方法
- voidArgumentTest 演示了如何获取 void 方法的参数
- answerTest 演示了如何截获 void 调用
3.9 级联 Mock
本例演示了如何自动 Mock 所有对象下的子对象
import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import org.junit.Test; public class DeepMockTest { @Test public void deepstubsAutoTest() { Account account = mock(Account.class, RETURNS_DEEP_STUBS); when(account.getRailwayTicket().getDestination()).thenReturn("Beijing"); account.getRailwayTicket().getDestination(); verify(account.getRailwayTicket()).getDestination(); assertEquals("Beijing", account.getRailwayTicket().getDestination()); } @Test public void deepstubsManualTest() { Account account = mock(Account.class); RailwayTicket railwayTicket = mock(RailwayTicket.class); when(account.getRailwayTicket()).thenReturn(railwayTicket); when(railwayTicket.getDestination()).thenReturn("Beijing"); account.getRailwayTicket().getDestination(); verify(account.getRailwayTicket()).getDestination(); assertEquals("Beijing", account.getRailwayTicket().getDestination()); } public class RailwayTicket { private String destination; public String getDestination() { return destination; } public void setDestination(String destination) { this.destination = destination; } } public class Account { private RailwayTicket railwayTicket; public RailwayTicket getRailwayTicket() { return railwayTicket; } public void setRailwayTicket(RailwayTicket railwayTicket) { this.railwayTicket = railwayTicket; } } }
代码说明如下:
- deepstubsAutoTest 演示了自动创建子对象的 Mock (推荐)
- deepstubsManualTest 演示了手动创建子对象的 Mock
3.10 部分 Mock
如下例所示,我们需要使用 Mock 跳过 Exception
import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.spy; import org.junit.Test; public class PartialMockTest { @Test public void partialMockTest() throws Exception { TestObj mockObj = spy(new TestObj()); doNothing().when(mockObj).m1(); mockObj.m3(); } class TestObj { public void m1() throws Exception { throw new Exception("exception"); } public void m2() { System.out.println("m2 is invoked"); } public void m3() throws Exception { m1(); m2(); } } }
我们使用了 spy 方法,它返回的对象是一个真实的对象,所有的方法调用也都是真的方法调用。但像例子中演示的,可以 Mock 掉指定的方法。如果有返回值,也可以和以前的例子一样使用 thenReturn。
4 FAQ
4.1 注意点
-
对于 @Mock 标注,MockitoAnnotations.initMocks(this); 一定要放在第一行
-
Mock 对象的所有方法均为假方法,而不是默认实现