我正在编写一个测试,目的是测试 Adapter
类(由 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 模拟支持类?
最佳答案
Android 支持类服从 the same rules as the rest of Java关于什么可以或不能被 mock ;不幸的是,您所指的方法(notifyDataSetChanged)是最终的。您可能还认为这是模拟您不拥有的类型的危险,因为final
语义会影响该类是否可以被模拟。
在内部,Mockito 通过创建您正在模拟的类的“子类”(实际上是代理实现)来工作。因为 final
方法解析可以在编译时发生,Java 跳过多态方法查找并直接调用您的方法。这在生产中稍快一些,但如果没有方法查找,Mockito 将失去其重定向调用的唯一方法。
这在 Android SDK 和支持库中很常见(最近用 a modified version of android.jar 解决了 final
修饰符)和支持库。您可能会考虑以下解决方案之一:
重构您的系统以引入您控制的包装器类,它非常简单,几乎不需要测试。
使用 Robolectric ,它使用一个特殊的以 Android-SDK 为目标的 JVM 类加载器,可以将调用重写为可模拟的等效项(并且还可以解析和提供 Android 资源,以便在没有模拟器的情况下进行测试)。 Robolectric 提供了足够多的特定于 Android 的“阴影”(替换实现),您通常可以避免编写自己的阴影,但也允许为支持库等情况自定义阴影。
使用 PowerMock ,它使用特殊的类加载器扩展了 Mockito 或 EasyMock,该类加载器还重写了被测系统的字节码。与 Robolectric 不同,它不是针对 Android 的,这使其成为一种更通用的解决方案,但通常需要更多设置。
PowerMock 是 Mockito 的扩展,因此它提供了更多的功能,但我个人尝试重构任何需要它的问题。
关于使用 Mockito 时不会模拟 Android 方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31777188/