Android 使用 Mockito 时不模拟方法

Android methods are not mocked when using Mockito

我正在编写一个测试,目的是测试 Adapter class 的 subclass 中的某些方法(由 RecyclerView 使用)。为了模拟适配器行为,我选择了 Mockito 库。

这是一个简单的测试:

import android.support.v7.widget.RecyclerView.Adapter;

public class TestAdapter {

    @Test
    public void testAdapter() throws Exception {
        Adapter adapter = Mockito.mock(Adapter.class);

        adapter.notifyDataSetChanged();
    }
}

什么都不应该发生,而是抛出 NullPointerException

java.lang.NullPointerException
    at android.support.v7.widget.RecyclerView$Adapter.notifyDataSetChanged(RecyclerView.java:5380)
    at pl.toro.test.adapter.TestAdapter.testAdapter(TestAdapter.java:38)
    ...

我也试过 spy 而不是 mock

@Test
public void testAdapterWithDoNothing() throws Exception {
    Adapter adapter = Mockito.spy(new Adapter() {...});
    doNothing().when(adapter).notifyDataSetChanged();
}

但这在第二行失败了(注意这只是一个模拟设置)

java.lang.NullPointerException
    at android.support.v7.widget.RecyclerView$AdapterDataObservable.notifyChanged(RecyclerView.java:8946)
    at android.support.v7.widget.RecyclerView$Adapter.notifyDataSetChanged(RecyclerView.java:5380)
    at pl.toro.test.adapter.TestAdapter.testAdapterWithDoNothing(TestAdapter.java:59)
    ...

是否可以在单元测试中使用 Mockito 模拟支持 classes?

Android支持classes服从the same rules as the rest of Java regarding what can or can't be mocked; unfortunately, the method you're referring to (notifyDataSetChanged)是最终的。您可能还认为这是 模拟您不拥有的类型的危险 ,因为 final 语义会影响 class 是否可以被模拟。

在内部,Mockito 的工作方式是创建一个 "subclass"(实际上是代理实现)您正在模拟的 class。因为 final 方法解析可以在编译时发生,所以 Java 跳过多态方法查找并直接调用您的方法。这在生产中稍快一些,但如果没有方法查找,Mockito 将失去其重定向调用的唯一方法。

这在 Android SDK(最近用 a modified version of android.jar 解决了 final 修饰符)和支持库中很常见。您可以考虑以下解决方案之一:

  1. 重构您的系统以引入您控制的包装器 class,它非常简单,几乎不需要测试。

  2. 使用 Robolectric,它使用一个特殊的 Android-SDK-targeted JVM classloader,它可以将调用重写为可模拟的等价物(并且还解析和提供Android 用于在没有模拟器的情况下进行测试的资源)。 Robolectric 提供了足够的 Android-特定 "shadows"(替换实现),您通常可以避免自己编写,但也允许为支持库等情况自定义阴影。

  3. 使用 PowerMock,它使用特殊的 classloader 扩展 Mockito 或 EasyMock,该加载器还会重写被测系统的字节码。与 Robolectric 不同,它不是 Android 目标,这使其成为更通用的解决方案,但通常需要更多设置。

    PowerMock 是 Mockito 的扩展,因此它提供了更多的功能,但我个人尝试重构任何需要它的问题。