我正在编写一个测试,其目的是测试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模拟支持类?
答案 0 :(得分:15)
Android支持类遵守the same rules as the rest of Java关于可以或不可以嘲笑的内容;不幸的是,你所指的方法(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的扩展,因此它提供了更严格的功能,但我个人试着围绕任何需要它的问题进行重构。