Espresso does no record any intent if there are no

2019-04-20 15:21发布

问题:

I'm trying to write a test to verify intent launching with espresso, the problem is that intended() does not record any intent.

I have this test

  @Test
public void shoulddosomething(){
    startActivity();
    intended(hasComponent(hasClassName(TemplatePictureCaptureActivity.class.getName())));

}

and in my activity i have this code

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(onRequestLayout());
    Intent intent = new Intent(this, TemplatePictureCaptureActivity.class);
    startActivity(intent);
}

The test result is this.

android.support.test.espresso.base.DefaultFailureHandler$AssertionFailedWithCauseError: Wanted to match 1 intents. Actually matched 0 intents.

IntentMatcher: has component: has component with: class name: is "cat.helm.recertel.ui.templatepicturecapture.TemplatePictureCaptureActivity" package name: an instance of java.lang.String short class name: an instance of java.lang.String

Matched intents:[]

Recorded intents:[]

I have tried to launch the intent inside onClickListen and it worked, but without it i can't get it to work. I also tried with idling resources with no luck. Do you know how to achieve this?

回答1:

The solution is to register an idling resource to wait the second activity.

In my case the test will remain as follows:

  @Test
public void shoulddosomething() {
    startActivity();
    String templatePictureActivityClassName = TemplatePictureCaptureActivity.class.getName();
    Espresso.registerIdlingResources(new WaitActivityIsResumedIdlingResource(templatePictureActivityClassName));
    intended(hasComponent(hasClassName(templatePictureActivityClassName)));
}

And here the idling resource.

 private static class WaitActivityIsResumedIdlingResource implements IdlingResource {
    private final ActivityLifecycleMonitor instance;
    private final String activityToWaitClassName;
    private volatile ResourceCallback resourceCallback;
    boolean resumed = false;
    public WaitActivityIsResumedIdlingResource(String activityToWaitClassName) {
        instance = ActivityLifecycleMonitorRegistry.getInstance();
        this.activityToWaitClassName = activityToWaitClassName;
    }

    @Override
    public String getName() {
        return this.getClass().getName();
    }

    @Override
    public boolean isIdleNow() {
        resumed = isActivityLaunched();
        if(resumed && resourceCallback != null) {
            resourceCallback.onTransitionToIdle();
        }

        return resumed;
    }

    private boolean isActivityLaunched() {
        Collection<Activity> activitiesInStage = instance.getActivitiesInStage(Stage.RESUMED);
        for (Activity activity : activitiesInStage) {
            if(activity.getClass().getName().equals(activityToWaitClassName)){
               return true;
            }
        }
        return false;
    }

    @Override
    public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
        this.resourceCallback = resourceCallback;
    }
}


回答2:

Try waiting a little before

intended(hasComponent(hasClassName(TemplatePictureCaptureActivity.class.getName(‌​))));


回答3:

This might be related to a race condition on the Intents component initialization and not with a race condition between the startActivity call and the usage of intended. If you start your activity from the SUT activity onCreate or onResume methods you should take a look at the following test rule.

I've created an IntentsTestRule fixing this issue. https://gist.github.com/pedrovgs/6a305ba4c5e3acfac854ce4c36558d9b

package com.aplazame.utils

import android.app.Activity
import androidx.test.espresso.intent.Intents
import androidx.test.rule.ActivityTestRule

class ExhaustiveIntentsTestRule<T : Activity> : ActivityTestRule<T> {

    private var isInitialized: Boolean = false

    constructor(activityClass: Class<T>) : super(activityClass)

    constructor(activityClass: Class<T>, initialTouchMode: Boolean) : super(activityClass, initialTouchMode)

    constructor(activityClass: Class<T>, initialTouchMode: Boolean, launchActivity: Boolean) : super(
        activityClass,
        initialTouchMode,
        launchActivity
    )

    override fun beforeActivityLaunched() {
        super.beforeActivityLaunched()
        Intents.init()
        isInitialized = true
    }

    override fun afterActivityFinished() {
        super.afterActivityFinished()
        if (isInitialized) {
            // Otherwise will throw a NPE if Intents.init() wasn't called.
            Intents.release()
            isInitialized = false
        }
    }
}

The main difference with the original IntentsTestRule implemented in AndroidX is the Intents.init() initialization. This time is invoked before starting the SUT activity. Keep in mind that this test rule will also record the intent used to start the SUT activity.