【问题标题】:How to properly spy on an Activity如何正确监视活动
【发布时间】:2016-02-19 00:35:13
【问题描述】:

当某些东西被监视时,Mockito 会创建一个代理实例。现在,有没有办法将在该代理实例上执行的 setter 转发到位于它后面的真实实例?

基本原理: 我有一个我无法完全控制的对象实例,即 Android 活动。我可以为我的应用程序的大部分部分提供代理版本,并且可以正常运行,但是因为我需要在活动的创建阶段很早就创建间谍/代理,它还没有完全实例化,例如基本上下文未附加。这发生在代理实例上,当然活动实例本身不使用(通过Activity.this 引用自身)。最终结果是这会导致各种崩溃,因为资源解析是通过这个基本上下文发生的,所以内部的 Fragment 机制会抛出 NPE 等等。

这里有一些代码:

public class CustomAndroidJUnitRunner extends AndroidJUnitRunner {
    @Override
    public Activity newActivity(ClassLoader cl, String className, Intent intent)
            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        Activity activity = super.newActivity(cl, className, intent);
        return maybeStubSomeDelegate(activity);
    }

    private Activity maybeStubSomeDelegate(Activity activity) {
        if (!(activity instanceof SomeDelegate)) {
            return activity;
        }
        Activity spiedActivity = spy(activity);
        doReturn(SomeDelegateMock.getInstance())
            .when((SomeDelegate) spiedActivity)
            .getDelegate();
        return spiedActivity;
    }
}

我一无所知 - 有什么想法吗?

【问题讨论】:

  • 你为什么不用浓缩咖啡:google.github.io/android-testing-support-library/docs/espresso 这样你也可以控制活动。
  • 我已经在使用 Espresso,这与我的问题无关。
  • 不可能向您的活动添加@VisibleForTesting setDelegate() 方法?我觉得任何涉及注入 Activity 间谍的方法总会有一些边缘情况,它不起作用。
  • 没有真正阅读整个问题,但 Mockito 的 thenCallRealMethod() 不是你想要的吗?
  • @Jozua 是的,我相信这就是 Mockito.spy() 已经在做的事情,因为它是 Mockito.mock(myInstance.getClass(), withSettings().spiedInstance(myInstance).defaultAnswer(Mockito.CALL_REAL_METHODS)); 的简写

标签: android mockito


【解决方案1】:

使用:

android 测试支持库的 SingleActivityFactory, ActivityTestRule 和莫基托的 spy()

dependencies {
  androidTestImplementation 'com.android.support.test:runner:1.0.2'
  androidTestImplementation 'com.android.support.test:rules:1.0.2'
  androidTestImplementation 'org.mockito:mockito-android:2.21.0'
}

大纲:

在 SingleActivityFactory 的实现中注入间谍实例

代码:

public class MainActivityTest {
    MainActivity subject;

    SingleActivityFactory<MainActivity> activityFactory = new SingleActivityFactory<MainActivity>(MainActivity.class) {
        @Override
        protected MainActivity create(Intent intent) {
            subject = spy(getActivityClassToIntercept());
            return subject;
        }
    };

    @Rule
    public ActivityTestRule<MainActivity> testRule = new ActivityTestRule<>(activityFactory, true, true);

    @Test
    public void activity_isBeingSpied() {
        verify(subject).setContentView(R.layout.activity_main);
    }

}

【讨论】:

  • 非常好!这一定是新的!
  • 玩了一会儿之后,我得出的结论是,在监视这样的活动时必须格外小心(假设您将与 UI 交互),因为您基本上绕过了Espresso 与 UI 的内置同步机制。所以,你必须延迟你的测试(即 Thread.sleep()),让 UI 完成它的事情并赶上测试,然后你才应该从间谍那里获得信息
  • 不,现在 ActivityTestRule 已被弃用,官方的 android 测试库似乎没有提供类似的功能来从工厂创建一个你可以监视的活动。文档建议使用 ActivityScenario 或 ActivityScenarioRule,但我还没有找到一种方法来使用它们来监视活动。
【解决方案2】:

您可以使用 Robolectric 为您的活动创建自己的代理(或 Robolectric 称它们为“阴影”),

当您创建代理时,您可以创建自己的设置器来触发真实对象方法,

如何创建阴影示例:

@Implements(Bitmap.class)
public class MyShadowBitmap {

@RealObject private Bitmap realBitmap;
private int bitmapQuality = -1;

@Implementation
public boolean compress(Bitmap.CompressFormat format, int quality, OutputStream stream) {
  bitmapQuality = quality;
  return realBitmap.compress(format, quality, stream);
}

public int getQuality() {
  return bitmapQuality;
}
}
}

当@RealObject 是你的真实实例时,

要使用 Robolectric 测试运行器使用此阴影,请定义一个新的测试类,如下所示:

@RunWith(RobolectricTestRunner.class)
@Config(shadows = MyShadowBitmap.class)
public class MyTestClass {}

要拉出当前的影子实例,请使用以下方法:

shadowOf()

无论如何,这里是 Robolectric 的链接:

http://robolectric.org/custom-shadows/

【讨论】:

  • 这可能对单元测试有用,但我实际上是在谈论仪器测试:)
  • 对于仪器测试也很有用(假设您不希望它们在设备\模拟器上运行)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-28
相关资源
最近更新 更多