Mockito 3.4.0 开始可 Mock 静态方法

Java 单元测试最趁手的 Mock 组件当属 Mockito,虽然它最初是基于继承来实现  Mock 的,所以对私有方法,私有属性,静态方法,final 类,final 方法,构造函数无能为力。于是有时不得不引入 JMockit 或 PowerMockit 来辅助。不过现在的 Mockito 功力有所增强。

首先是 Mockito 2.1.0 开始可以 Mock final 类和 final 方法,要在 classpath 下创建个文件 mockito-extensions/org.mockito.plugins.MockMaker, 内容为 mock-maker-inline。之前写过一篇介绍:Mockto 也能 Mock final 类和 final 方法了,其中也探索了它的实现细节,使用到了 ByteBuddy 修改字节码。

Mockito 3.4.0 通过类似的 mockto-extensions 扩展的方式,实现了对静态方法的 Mock。所有使用到的接口是 org.mockito.MockedStatic,它当前在 Mockito 3.7.7 中还是一个试验性方法 @Incubating,能拿来用就行。 阅读全文 >>

Mockito 的 anyString(), any(Foo.class) 等不能匹配 null 值

使用 Mockito Mock 方法式,一直以为可以用 anyString(), any(Foo.class) 等匹配 null 值,其实不行,null 值必须显式的用 null, 或 eq(null) 来匹配。anyString(), anyInt() 等只能匹配非 null 值,查看它们的返回值实际是 "" 和 0 等, 而更为特别的是 any(Foo.class) 看到的是 null, 仍然不能匹配 null 值。进一步用 Mockito.mockingDetails(mock).printInvocations() 打印出的内容,anyString(), any(Foo.class) 都会显示为 null 值。

说的有点罗嗦,看下面的例子, 被测试类 UserDao,sql 和 sqlArguments 由各自的 setter 方法来控制,默认它们都为 null 阅读全文 >>

跳过构造函数创建 Java 对象(测试)

如果一个 Java 类在初始化时会有外部依赖,这就给单元测试创建它的实例时造成困难。当然被测试类可以改造为依赖全部构造时注入或创建实例后延迟注入,这里不考虑这种改造。

可以参看我以前一篇类似的日志:使用 JMockit 来 mock 构造函数

来说下面的例子

假如上面的代码是不能改动的,并且在 new PriceInquiry() 时依赖于网络环境,所以单机情况不能创建成功。也就使得测试时试图

new OrderService();

会失败。并且试图用 Mockito 的 @InjectMocks 也不行 阅读全文 >>

Mockito 也能 Mock final 类和 final 方法了

以实际 Java 项目中的单元测试 Mock 框架基本是 Mockito 2 了,因为它有一个十分流畅的 API。Mockito  2也为 JUnit 5 配上了 MockitoExtension, 所以 JUnit 5 下使用 Mockito 2 的关节也打通了。但在我们享受 Mockito 2 便利的同时,与 JMockit 相比局限性就很明显,因为 Mockito 2 是通过创建匿名子类来进行 Mock 的,所以任何子类对父类无法突破的方面它都存在。譬如,final 类, final 方法, 私有方法, 静态方法, 构造函数都是无法通过子类型进行重写的。所以除非特别需要,在 Mockito 2 无法胜任时都求助于 JMockit,JMockit 借助于 javaagent 取得了 JVM 的高控制权才得已为所欲为。

当 Mockito 来到了 2.1.0 版本,它也觉得不能对以上所有的限制置若罔闻, 首先带给我们的突破是它也可以 Mock final 类和 final 方法,虽然仍处于孵化器中,但毕竟是应用在单元测试中,能用就很不错了,只要以后不被拿走就行。这是官方对它的介绍 Mock the unmockable: opt-in mocking of final classes/methods

下面我亲自操作一遍,并给出更全方位的测试样例 阅读全文 >>

JUnit 5 使用 Mockito 2 进行测试

JUnit 5 刚出来那时,也就是第一个版本 5.0.0 时,还不能很好的支持 Mockito 的测试,因为 Mockito 没能跟得那么紧密。那时候 JUnit 5 只能试验性的提供了一个极不正式的 com.example.mockito.MockitoExtension, 看那包名就知道不是来真的,所以决定再等。JUnit 5 不再原生支持 JUnit 4 的 Rule,一切都将是 Extension,那也是要求 Mockito 能够与之俱进。现在等来了,JUnit 5 进化到了 5.2.0, Mockito 也早已有了一个单独的模块 mockito-junit-jupiter 来迎接它。

在 Mockito 2.1.0 的 What's new in Mockito 2 中记述了 JUnit 5 为 Mockito 2 开发了一个 MockitoExtension。追溯到 Mockito 2 的 Release Notes, 我们发现 Mockito 2 官方最早引入 MockitoExtension 的版本是 2.16.3(2018-03023)。我对 Mockito 对 JUnit 5 支持的最新更新是从这个 Pull Request MockitoExtension for JUnit5 得知的。

一句话讲就是现在的 Mockito 2 有原生态的 MockitoExtension 来支援 JUnit 5, 可以非常放心可靠的让 JUnit 5 和 Mockito 2 一起稳定工作。因此前面那个包名带 example 字样的 MockitoExtension 链接也就无效了。 阅读全文 >>

使用 Mockito 的 @InjectMocks 创建被测试类实例

初识 Mockito 这个测试框架后,我们要使用 Mock 的属性创建一个被测试类实例时,大概会下面这么纯手工来打造。

假定类 UserService 有一个属性 UserDao userDao, 需要构造 UserService 实例时 Mock 内部状态

UserDao userDao = Mockito.mock(UserDao.class);
UserService testMe = new UserService(userDao);

如此,userDao 的行为就可以自由模拟了,这种纯手工方式都不需要给测试类添加

@RunWith(MockitoJunitRuner.class)
//或
MockitoAnnotations.initMocks(this);

因为上面两句是给 Mockito 的注解使用的。

如果所有的 Mock 对象全部通过手工来创建,那就不容易体现出 Mockito 的优越性出来。因此对于被测试对象的创建,Mock 属性的注入应该让 @Mock 和 @InjectMocks 这两个注解大显身手了。

标注在实例变量上的 @Mock 相当于是 Mockito.mock(Class) 创建了一个 Mock 对象,而 @InjectMock 标的实例会寻找到相应 Mock 属性想法构造出被测试类的实例。看下面的例子: 阅读全文 >>

Mockito 中被 Mocked 的对象属性及方法的默认值

在 Java 测试中使用 Mockito 有段时日了,以前只是想当然的认为 Mock 的对象属性值和方法返回值都是依据同样的规则。基本类型是 0, 0.0, 或 false, 对象类型都是 null, Mock 对象的默认返回值也应该是一样的。直到最近有一天,有一个返回 Optional<String> 类型的方法,由于忘记对该方法打桩,意外的发现它返回的不是 null, 而 Optional.empty(), 因此才意识到此处定有蹊跷。着实有必要用代码验证一下 Mockito 是怎么决定属性及方法的各种返回类型的默认值的。

此次测试所用的 Mockito 版本是 mockito-core-2.12.0.

于是创建了下面一个类 MyClass 用于生成 Mock 对象,选取了一些典型的数据类型, 包括 int, Double, String, long[], Optional<String>, Collection<String>, Map<String, String>, 同时测试 Mock 对象默认的属性值与方法默认返回值。 阅读全文 >>

使用 Mockito 修改私有属性

修改私有属性来 Mock 可能不是一种很好的测试方式, 因为属性名是动态的,但有时不得已而为了,例如下面的代码:

public class UserService {
    private ExternalApi external = ExternalApi.default();
    private UserDao userDao;

    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    public User findUserById(int id) {
        return userDao.findById(external.convertId(id));
}

测试时欲隔离对 ExternalApi 的外部依赖, 当然可以把它也作为构造函数的一个参数,这样创建 UserService 实例时就可以 Mock external 属性。不过 external 经常是不变的,所以作为方法参数的必要性也不大。这就希望能在构造出 UserService 之后对 external 私有属性进行 Mock 处理。

在 Mockito 1.x 和 2.x 下要使用不同的方式,分别使用到 Whitebox 和 FieldSetter 类,它们都来自于  mockito.internal.util.reflection 包,可见 Mockito 打心底不推荐直接使用它们,但谁叫它们是 public 的呢。还有一种方式是使用 PowerMock + Mockito, 这是后话。 阅读全文 >>

Mockito 如何 mock 返回值为 void 的方法

最初接触 Mockito 还思考并尝试过如何用它来 mock 返回值为 void 的方法,然而 Google 查找到的一般都会说用 doThrow() 的办法

doThrow(new RuntimeException()).when(mockObject).methodWithVoidReturn();

因为无法使用常规的 when(mockObject.foo()).thenReturn(...) 的方法。

当时我就纳闷,为何我想 mock 一个返回值为 void 的方法,却是在模拟抛出一个异常,现在想来如果一个返回值为 void 的方法,为何要去 mock 这个方法呢?

回想一个我们要 mock 一个方法的意图是什么:

  1. 在特定输入参数的情况下期待需要的输出结果(返回值)
  2. 在方法抛出某种类型异常调用者作出的反应

对于 void 返回值的方法,如果要验证有没有被调用过几次可以在事后用 verify() 方法去断言。所以基本上对于 void 返回值的方法一般可不用去 mock 它,只需用  verify() 去验证,或者就是像前面一样模拟出现异常时的情况。

所以本文并不像是去直接回答标题所示的问题: Mockito 如何 mock  返回值为  void 的方法,而是如何应对 mock  对象的  void 方法 阅读全文 >>

Mockito 中捕获 mock 对象方法的调用参数

Mockito 可以帮助我们创建 Mock 对象,mock 被调用的方法,断言调用次数,在方法参数不易确定的情况下还能帮我们捕获参数。下面是我们第一个问题:

为什么要捕获调用参数

在被 mocker 方法调用参数明确的情况下可无需捕获参数,例如,下面的情景:

如果 UserService 的 save(user) 最终操作的不是同一个对象,它的实现稍加变化如下 阅读全文 >>