tom*_*ozb 5 android unit-testing mockito
我正在编写一个测试,目的是测试类的子类中的一些方法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();
}
}
Run Code Online (Sandbox Code Playgroud)
什么都不应该发生,而是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)
...
Run Code Online (Sandbox Code Playgroud)
我也试过spy
而不是mock
@Test
public void testAdapterWithDoNothing() throws Exception {
Adapter adapter = Mockito.spy(new Adapter() {...});
doNothing().when(adapter).notifyDataSetChanged();
}
Run Code Online (Sandbox Code Playgroud)
但这在第二行失败(注意这只是一个模拟设置)
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)
...
Run Code Online (Sandbox Code Playgroud)
是否可以在单元测试中使用Mockito模拟支持类?
Jef*_*ica 16
Android支持类遵循与其他Java相同的规则,关于可以或不可以嘲笑的内容; 不幸的是,你所指的方法(notifyDataSetChanged)是最终的.您可能还认为这是您不拥有的模拟类型的危险,因为final
语义会影响是否可以模拟类.
在内部,Mockito通过创建您正在嘲笑的类的"子类"(实际上是代理实现)来工作.因为final
方法解析可以在编译时发生,所以Java会跳过多态方法查找并直接调用您的方法.这在生产中稍快一些,但如果没有方法查找,Mockito就会失去重定向呼叫的唯一方法.
这是Android SDK中的一个常见现象(最近解决了android.jar的修改版本,其中final
修饰符被剥离)和支持库.您可以考虑以下解决方案之一:
重构您的系统以引入您控制的包装类,这很简单,几乎不需要测试.
使用Robolectric,它使用一个特殊的Android-SDK定位的JVM类加载器,可以将调用重写为可模拟的等价物(并且还可以解析和提供Android资源,无需模拟器进行测试).Robolectric提供了足够的特定于Android的"阴影"(替换实现),您通常可以避免编写自己的阴影,但也允许为支持库等情况提供自定义阴影.
使用PowerMock,它使用一个特殊的类加载器扩展Mockito或EasyMock,该类加载器也会重写被测系统的字节码.与Robolectric不同,它不是针对Android的,因此它是一种更通用的解决方案,但通常需要更多设置.
PowerMock是Mockito的扩展,因此它提供了更严格的功能,但我个人试着围绕任何需要它的问题进行重构.