使用Mockito时不会嘲笑Android方法

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修饰符被剥离)和支持库.您可以考虑以下解决方案之一:

  1. 重构您的系统以引入您控制的包装类,这很简单,几乎不需要测试.

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

  3. 使用PowerMock,它使用一个特殊的类加载器扩展Mockito或EasyMock,该类加载器也会重写被测系统的字节码.与Robolectric不同,它不是针对Android的,因此它是一种更通用的解决方案,但通常需要更多设置.

    PowerMock是Mockito的扩展,因此它提供了更严格的功能,但我个人试着围绕任何需要它的问题进行重构.

  • 我认为这可能是支持库没有像androd.jar那样被替换的情况:"在运行时,测试将针对android.jar的修改版本执行,其中所有最终修饰符都已被剥离".我正在测试在构造函数中使用`Adapter`的类,但是我需要验证在这个适配器上调用notify方法所以1.解决方案对我没有帮助.我已经在使用Roblectric,但它太慢了,这就是我试图嘲笑它的原因.我熟悉PowerMock,但我不想添加额外的测试依赖 - 看起来我别无选择.谢谢杰夫! (2认同)