使用Mockito时不会嘲笑Android方法

时间:2015-08-02 22:18:33

标签: 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();
    }
}

不会发生任何事情,但会引发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模拟支持类?

1 个答案:

答案 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修饰符已被删除)和支持库。您可以考虑以下解决方案之一:

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

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

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

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