Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Espresso Test Failed: Wanted to match 1 intents. Actually matched 0 intents

I am trying to test that my SplashActivity correctly launches HomeActivity if the user is logged in.

I have looked at related questions on StackOverflow, this seems to be a common issue, but I just cannot seem to get anything to work.

I have watched the test execute on my device and visually verified that SplashActivity is indeed launching HomeActivity.

HomeActivity expects a plain intent with no data.

Here is the full error:

IntentMatcher: has component: has component with: class name: is "com.shoeboxed.fetch.presentation.ui.activities.HomeActivity" package name: an instance of java.lang.String short class name: an instance of java.lang.String

Initial Attempt:

public class SplashActivityTest extends EspressoIntegrationTest {

    @Mock
    UserRepository userRepository;

    @Rule
    public IntentsTestRule<SplashActivity> activityRule = new IntentsTestRule<>(SplashActivity.class, true, false);

    @Test
    public void loggedInUser() {
        User user = Fakes.user();
        when(userRepository.getUser()).thenReturn(user);
        doNothing().when(userRepository).refreshTeam();

        activityRule.launchActivity(new Intent());
        intended(hasComponent(HomeActivity.class.getName()));

        verify(userRepository, times(1)).getUser();
        verify(userRepository, times(1)).refreshTeam();
    }
}

Second Attempt: (Trying different matching syntax)

public class SplashActivityTest extends EspressoIntegrationTest {

    @Mock
    UserRepository userRepository;

    @Rule
    public IntentsTestRule<SplashActivity> activityRule = new IntentsTestRule<>(SplashActivity.class, true, false);

    @Test
    public void loggedInUser() {
        User user = Fakes.user();
        when(userRepository.getUser()).thenReturn(user);
        doNothing().when(userRepository).refreshTeam();

        activityRule.launchActivity(new Intent());
        intended(hasComponent(new ComponentName(getTargetContext(), HomeActivity.class)));

        verify(userRepository, times(1)).getUser();
        verify(userRepository, times(1)).refreshTeam();
    }
}

Third Attempt (Adding an Idling Resource on the destination activity)

public class SplashActivityTest extends EspressoIntegrationTest {

    @Mock
    UserRepository userRepository;

    @Rule
    public IntentsTestRule<SplashActivity> activityRule = new IntentsTestRule<>(SplashActivity.class, true, false);

    @Test
    public void loggedInUser() {
        User user = Fakes.user();
        when(userRepository.getUser()).thenReturn(user);
        doNothing().when(userRepository).refreshTeam();

        WaitActivityIsResumedIdlingResource resource = new WaitActivityIsResumedIdlingResource(HomeActivity.class.getName());
        Espresso.registerIdlingResources(resource);

        activityRule.launchActivity(new Intent());
        intended(hasComponent(new ComponentName(getTargetContext(), HomeActivity.class)));

        verify(userRepository, times(1)).getUser();
        verify(userRepository, times(1)).refreshTeam();
        Espresso.unregisterIdlingResources(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(IdlingResource.ResourceCallback resourceCallback) {
        this.resourceCallback = resourceCallback;
    }
}

Here is my base test. It registers an idling resource on my background processes (clean architecture use cases):

public class EspressoIntegrationTest {

    private static IdlingResource idlingThreadPool;

    private AppComponent oldComponent = app().appComponent();

    @Rule
    public DaggerMockRule<AppComponent> daggerRule = new DaggerMockRule<>(AppComponent.class, new AppModule(app()))
            .set(component -> {
                oldComponent = app().appComponent();
                app().setAppComponent(component);
            });

    @BeforeClass
    public static void registerResources() {
        idlingThreadPool = getIdlingThreadExecutor();
        Espresso.registerIdlingResources(idlingThreadPool);
    }

    @AfterClass
    public static void deregister() {
        Espresso.unregisterIdlingResources(idlingThreadPool);
    }

    @After
    public void resetApp() {
        app().setAppComponent(oldComponent);
    }

    private static IdlingResource getIdlingThreadExecutor() {
        return (IdlingResource) jobExecutor().getThreadPoolExecutor();
    }

    private static JobExecutor jobExecutor() {
        return ((JobExecutor) app().appComponent().threadExecutor());
    }

    private static App app() {
        return (App) getInstrumentation().getTargetContext().getApplicationContext();
    }
}
like image 786
madcow Avatar asked Feb 22 '17 19:02

madcow


1 Answers

Asserts that the given matcher matches one and only one intent sent by the application under test. This is an equivalent of verify(mock, times(1)) in Mockito. Verification does not have to occur in the same order as the intents were sent. Intents are recorded from the time that Intents.init is called

See documentation

When you use IntentsTestRule Intents.init() will complated after activity created. As far as I understand, you starting HomeActivity in SplashActivity.onCreate and finish SplashActivity.

So you can use ActivityTestRule and call Intents.init() before launching activity, like this:

public class SplashActivityTest extends EspressoIntegrationTest {

    @Mock
    UserRepository userRepository;

    @Rule
    public ActivityTestRule<SplashActivity> activityRule = new ActivityTestRule<>(SplashActivity.class, true, false);


    @Before
    public void setUp() throws Exception{
        Intents.init();
    }

    @Test
    public void loggedInUser() {
        User user = Fakes.user();
        when(userRepository.getUser()).thenReturn(user);
        doNothing().when(userRepository).refreshTeam();

        activityRule.launchActivity(new Intent());
        intended(hasComponent(HomeActivity.class.getName()));

        verify(userRepository, times(1)).getUser();
        verify(userRepository, times(1)).refreshTeam();
    }


    @After
    public void tearDown() throws Exception{
        Intents.release();
    }
}
like image 140
Berdimurat Masaliev Avatar answered Sep 22 '22 05:09

Berdimurat Masaliev