Mockito-提高单元测试效率利器

Mockito-提高单元测试效率利器

前言

之前在开发进行到写单元测试阶段的时候,发现要测试的方法里面是包含依赖的:外部接口RPC调用、DB调用。在某些情况下,部分依赖不稳定或者无法在测试环境调用时,会导致用例偶尔执行失败。

另外一点,很多用例都是在测试用例的开头写了`@SpringRunTest`的注解,导致跑用例的时候会启动整个Spring容器,这样一来,运行测试用例就非常慢了。当在一些比较大的项目运行用例时,甚至达到了每次启动容器需要5-6分钟的时长,渐渐就有点受不了这种操作,每改一行代码心里都焦急,因为如果错了的话又要再等5-6分钟才能看到效果了。后来请教同事和上网搜索,找到了一种比较快且安全的方案,使用Mock框架-- Mockito ,学习并实践了一段时间,总结一下使用方法。

Mockito

Mockito是当前最流行的单元测试Mock框架。

什么是Mock
Mock的字面意思就是模仿,虚拟,在单元测试中,使用Mock可以虚拟出一个外部依赖对象。

对于在单元测试中一些不容易构造或者不容易获取的对象(如外部服务),用一个Mock对象来创建,可以降低测试的复杂度,只关心当前单元测试的方法。

为什么用Mock
单元测试的目的就是为了验证一个代码单元的正确性,真正要验证的只是某个输入对应的输出的正确与否。如果把外部依赖服务引入进来,就会增加原来单元的复杂度,且在该单元中隐形地掺杂了其他功能的内容。

使用Mock对象进行单元测试,开发可以只关心要测试单元的代码。


使用示例

先看看代码示例,假设有以下的场景:
1、验证获取用户信息接口:包含用户ID、用户昵称、是否vip
2、是否vip需要外部服务VIPService获取,通过RPC调用,测试环境如果机器性能较差或者网络不好会导致用例不稳定
3、编写单元测试判断用户VIP信息返回是否正确

需求是判断获取用户信息接口返回的格式是否正确,与vip接口的返回值无关,只要透传vip接口返回的字段即可,测试代码如下:

@RunWith(PowerMockRunner.class)
public class UserServiceTest {
    @InjectMocks
 private UserService userService;
    @Mock
 private VIPService vipService;
    @Test
 public void getUserInfo() {
 Mockito.when(vipService.isVip(Mockito.anyString())).thenReturn(true);
 Result result = userService.getUserInfo("123");
 Assert.assertEquals(true, result.getData().get("isVip"));
}

解释下上面代码用到的几个注解

@Mock :创建一个Mock

@InjectMocks :Mock一个实例,其余用Mock注解创建的mock将被注入到该实例中。

Mockito.when(...).thenReturn(...) :Mock方法,如果满足when里面的条件,返回thenReturn指定的结果。

在这段代码里,使用 @Mock 注解创建了一个VipService实例,使用`@InjectMock`创建了UserService,Mock创建的vipService实例会被注入到UserService的实例中,在写测试用例的时候就可以模拟vipService的行为。

`Mockito.when(vipService.isVip(Mockito.anyString())).thenReturn(true);`
这段代码表示不管传任何参数给vipService.isVip方法,该方法都会返回true,这样,就不影响获取用户信息接口的正常测试,也可以使用断言验证返回的数据。


遇到过的场景

以上是使用Mockito实践最简单的示例,在生产环境使用过程中,会有各种各样的需求需要满足,下面列一下笔者遇到过的场景。

mock异常
这种场景是,方法里面声明了可能会抛出A异常,而A异常有多种可能性,不同的异常对应不同的message,为了验证抛出某种A异常后的功能,就需要模拟方法抛出指定message的A异常。

使用方式是定义一个 Rule 注解的属性,在使用时,设置thrown抛出的异常类型和所带的message。简要代码如下:

class AException extends RuntimeException {
 private final int code;
 public AException(int code, String msg) {
 super(msg);
 this.code = code;
@RunWith(PowerMockRunner.class)
public class MockExceptionTest {
    @Rule
 public ExpectedException thrown = ExpectedException.none();
    @Test
 public void mockException() {
 thrown.expect(AException.class);
 thrown.expectMessage("expected message");
 // test code
}

mock空方法
mock一个空方法,比较简单,就是调用 doNothing().when()...

mock静态方法
如果要Mock静态方法,首先在类的开头增加注解 @PrepareForTest({ClassNameA.class})

在需要Mock类方法的之前,增加代码: PowerMockito.mockStatic(ClassNameA.class); ,然后就可以愉快的Mock了。简要代码如下:

class ClassNameA {
 public static int methodA() {
 // code
 return ret;
@RunWith(PowerMockRunner.class)
@PrepareForTest({ClassNameA.class})
public class MockStaticClassTest {
    @Test
 public void mockStaticMethod() {
 PowerMockito.mockStatic(ClassNameA.class);