在Android Instrumentation测试中获取当前活动(Activity)

7

我的Android应用程序中的MainActivity检查用户是否已登录(这些信息存储在SharedPreferences中),如果没有登录,则将用户带到LoginActivity。我正在尝试使用以下代码进行测试:

public class MainActivityTest extends ActivityInstrumentationTestCase2<MainActivity> {

private static final int TIME_OUT = 5000; /* miliseconds */

private MainActivity mMainActivity;
private Instrumentation mInstrumentation;
private SharedPreferences mLoginPrefs;

public MainActivityTest() {
    super(MainActivity.class);
}

protected void setUp() throws Exception {
    super.setUp();

    setActivityInitialTouchMode(false);

    mMainActivity = getActivity();
    mInstrumentation = getInstrumentation();
    mLoginPrefs = mInstrumentation.getTargetContext().getSharedPreferences(LoginActivity.PREFS_NAME, Context.MODE_PRIVATE);

    SharedPreferences.Editor editor = mLoginPrefs.edit();
            // User is not logged in, so it should be redirect to LoginActivity
    editor.putBoolean("logged_in", false);
    editor.commit();
}

//...

public void testC_OpenLoginActivityIfUserIsNotLoggedIn() {
    ActivityMonitor monitor = mInstrumentation.addMonitor(LoginActivity.class.getName(), null, false);
    Activity nextActivity = mInstrumentation.waitForMonitorWithTimeout(monitor, TIME_OUT);

    assertNotNull(nextActivity);
    nextActivity.finish();

    SharedPreferences.Editor editor = mLoginPrefs.edit();
            // Login the user so we can continue the tests
    editor.putBoolean("logged_in", true);
    editor.commit();
}

但是这种方法不起作用,LoginActivity会打开但是waitForMonitorWithTimeout从未返回,所以我被卡在了LoginActivity上(我需要回到MainActivity来进行其他测试)。
类似于这个SO问题的代码适用于按钮点击,但是这个Activity不是由任何点击加载的,所以我认为可能没有时间让监视器工作。
我只需要一种方法来获取实际的Activity,这样我就可以进行断言并使其完成以继续我的测试。
还有一件事:如果可能的话,我更喜欢不使用Robotium的方法。
3个回答

11

为了解决您的问题,首先看一下对于您的测试最重要的两种方法:

Instrumentation#addMonitor(java.lang.String, android.app.Instrumentation.ActivityResult, boolean)
Instrumentation.ActivityMonitor#waitForActivity()

根据Android API参考:

addMonitor

添加一个新的Instrumentation.ActivityMonitor,每次启动一个Activity时都会被检查。该监视器将在任何现有监视器之后添加;只有当没有任何现有监视器可以处理Intent时,该监视器才会被触发。

waitForActivity

阻塞直到创建了与此监视器匹配的Activity,然后返回结果活动。


现在让我们更加清晰明了一些。

addMonitor应该始终在预期启动Activity之前调用,不能太晚。

waitForActivity应该仅在预期启动Activity之后调用,不能太早,因为它会阻塞。


回到你的代码:

你同时调用了它们两个,没有任何魔法发生在它们之间。所以,对于addMonitor来说可能太晚了,对于waitForActivity来说可能太早了。

ActivityMonitor monitor = mInstrumentation.addMonitor(LoginActivity.class.getName(), null, false);
Activity nextActivity = mInstrumentation.waitForMonitorWithTimeout(monitor, TIME_OUT);

如果在调用waitForActivity之前太早,它将会被阻塞并失败直到超时(因为预期的活动尚未启动),而您将永远无法看到预期的活动被启动。

如果调用addMonitor太晚,监视器将在预期的活动启动后启动,并且自那时以来预期的活动不会再次启动,因此waitForActivity会因为监视器未命中而被阻塞。

因此,两种情况之间的区别在于是否启动了预期的活动。对于您的情况,我认为现在调用addMonitor已经太晚了。

解决方案很简单:只需将addMonitor移到足够早的位置,即在您的LoginActivity启动之前的setUp方法中移动它,就像这样:

mInstrumentation = getInstrumentation();
ActivityMonitor monitor = mInstrumentation.addMonitor(LoginActivity.class.getName(), null, false);

顺便提一下,对于您的情况,有超时或没有超时都不重要。

不要忘记在不需要监视器的时候将monitor移除,例如:

@Override
protected void tearDown() throws Exception {
    mInstrumentation.removeMonitor(monitor);
    super.tearDown();
}

1
使用Kotlin,addMonitor()函数中的类名应该怎么传递?我尝试了"com.locuslabs.sdktestapp.MapActivity"MapActivity::class.java.name.toString()MapActivity::class.java.canonicalName.toString()等等,但是waitForMonitorWithTimeout()返回的结果都是null - Michael Osofsky

4
public static Activity getCurrentActivity() {
    final Activity[] currentActivity = {null};
    getInstrumentation().runOnMainSync(new Runnable() {
        public void run() {
            Collection resumedActivities = ActivityLifecycleMonitorRegistry.getInstance()
                    .getActivitiesInStage(RESUMED);
            if (resumedActivities.iterator().hasNext()) {
                currentActivity[0] = (Activity) resumedActivities.iterator().next();
            }
        }
    });
    return currentActivity[0];
}

1
这个解决方案适用于我的 Kotlin 应用程序,谢谢。 - Michael Osofsky
由于某些原因,.getActivitiesInStage(RESUMED) 只返回了一个空集合。这个方法只能用于检测同一包中的活动吗? - WatashiJ

-1
你走在了正确的轨道上。你需要调用的方法是:
monitor.waitForActivityWithTimeout(TIME_OUT);

替代

mInstrumentation.waitForMonitorWithTimeout(monitor, TIME_OUT);

根据API参考,他所做的是正确的:waitForMonitorWithTimeout(Instrumentation.ActivityMonitor monitor,long timeOut) - Jing Li

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接