1. Mock 的基本介绍
1.1 mock的定义:
mock是在测试过程中,对于一些不容易构造/获取的对象,创建一个mock对象来模拟对象的行为。
1.2 为什么要使用mock
在做单元测试过程中,经常会有以下的场景:
class A 依赖 class B
class B 依赖 class C和class D
class C 依赖 ...
class D 依赖 ...
1.被测对象依赖的对象构造复杂
我们想对class A进行单元测试,需要构造大量的class B、C、D等依赖对象,他们的构造过程复杂(体现在构造步骤多、耗时较长),这时我们可以利用mock去构造虚拟的class B、C、D对象用于class A的测试,因为我们只是想测试class A的行为是否符合预期,我们并不需要测试依赖对象。2.被测单元依赖的模块尚未开发完成,而被测对象需要依赖模块的返回值进行测试:
----- 比如service层的代码中,包含对dao层的调用,但dao层代码尚未开发
----- 比如web的前端依赖后端接口获取数据进行联调测试,但后端接口并未开发完成

1.3 哪些时机和场合需要使用mock
- 1.单元测试/接口测试中测试对象依赖其他对象,这些对象的构造复杂、耗时或者根本无法构造(未交付)
- 2.我们只测试对象内部逻辑的质量,不关心依赖对象的逻辑正确性和稳定性
1.4 是否应该在测试中使用mock,投入产出比如何
我们可以基于以下2个原则去做mock,这样的mock的投入产出比是最高的
1.不需要对所有的依赖对象/服务进行mock,只对那些构造步骤复杂、构造耗时较长、不稳定的依赖对象/服务进行mock。
2.如果做分层测试(比如分层自动化),高层的测试设计可以基于以下假设:低层的测试已保证低层对象的质量,高层对低层的依赖可以mock,无需关心所依赖的低层对象的质量。
1.5 Local Mock和Remote Mock
Local Mock和Remote Mock的区别在于依赖对象的行为调用是本地/远程调用
1.Mock本地依赖对象
如果依赖对象的调用是本地调用,就属于Local Mock。EasyMock/Mockito都可以实现对本地依赖对象的Mock。2.Remote Mock
如果被测对象依赖对象的是一些远程服务(HTTP/RPC服务),就属于Remote Mock,Remote Mock一般实现对指定的服务/接口、客户端(消费者)进行Mock。可以通过Mock Server等方式去实现。

LocalMock相当于使用Mock对象替换依赖对象。
RemoteMock相当于使用MockServer替换真实的HTTP/RPC Server。
2. Mock工具
1.Mockito
Mockito参考
2.EasyMock
EasyMock参考
3.PowerMock
PowerMock官网
3. Mockito 的使用
Mockito 是一个流行的 mock 框架,可以与 JUnit 结合使用,Mockito 允许我们创建和配置 mock 对象,使用 Mockito 将大大简化了具有外部依赖项的类的测试开发。spring-boot-starter-test 中默认集成了 Mockito,不需要额外引入。
在测试中使用 Mockito,通常会:
mock 外部依赖关系并将 mock 对象插入待测代码
执行被测代码
-
验证代码是否正确执行
3.1 使用 Mockito 创建 mock 对象
Mockito提供了几种创建 mock 对象的方法:
使用静态 mock() 方法
使用 @Mock 注解
对于 @Mock,@Spy, @InjectMocks等注解的成员变量初始化到目前为止有2种方法:
- 对 JUnit 测试类添加
@RunWith(MockitoJUnitRunner.class) - 在
@Before方法内调用初始化方法:MockitoAnnotations.initMocks(Object)
现在,正如我们上面的测试用例所示,对于@Mock等注解的成员变量的初始化又多了一种方法:MockitoRule
规则MockitoRule会自动帮我们调用MockitoAnnotations.initMocks(this)去实例化出注解的成员变量,我们就无需手动进行初始化了。
public class UnitTest3 {
// 触发创建带有 @Mock 注解的对象
@Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
// 1. 使用 @Mock 注解创建 mock 对象
@Mock private UnitDao unitDao;
@Test
public void test() {
// 2. 使用静态 mock() 方法创建 mock 对象
Iterator iterator = mock(Iterator.class);
// when...thenReturn / doReturn...when 模拟依赖调用
when(iterator.next()).thenReturn("hello");
doReturn(1).when(unitDao).delete(anyLong());
// 断言
Assert.assertEquals("hello", iterator.next());
Assert.assertEquals(new Integer(1), unitDao.delete(1L));
}
}
3.2 使用 mock 对象实践单元测试
我们要单元测试的内容,常常包含着对数据库的访问等等,那么我们要如何 mock 掉这部分调用呢?我们可以使用 @InjectMocks 注解创建实例并使用 mock 对象进行依赖注入。
@Service
public class UnitServiceImpl implements UnitService {
@Autowired
private UnitDao unitDao;
@Override
public String sayHello() {
Integer delete = unitDao.delete(1L);
System.out.println(delete);
return "hello unit";
}
}
@RunWith(MockitoJUnitRunner.class)
public class UnitTest2 {
@Mock
private UnitDao unitDao;
@InjectMocks
private UnitServiceImpl unitService;
@Test
public void unitTest() {
// mock 调用
when(unitDao.delete(anyLong())).thenReturn(1);
Assert.assertEquals("hello unit", unitService.sayHello());
}
}
3.3 使用 PowerMock mock 静态方法。
Mockito 也有一些局限性。例如:不能 mock 静态方法和私有方法。这个时候我们就要用到 PowerMock,PowerMock 支持 JUnit 和 TestNG,扩展了 EasyMock 和 Mockito 框架,增加了mock static、final 方法的功能。
首先需要引入 PowerMock 的依赖:
<!-- PowerMock -->
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>2.0.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>2.0.7</version>
</dependency>
接下来就能愉快的 mock 静态方法了。
@RunWith(PowerMockRunner.class)
@PrepareForTest({StringUtils.class})
public class UnitTest4 {
@Test
public void test() {
mockStatic(StringUtils.class);
when(StringUtils.getFilename(anyString())).thenReturn("localhost");
Assert.assertEquals("localhost", StringUtils.getFilename(""));
}
}
