9

I'm working now in my functional tests and in one of them I have to test that a toast message is NOT shown. Considering this is the code I'm using to check if the toast message is shown (this code works):

onView(withText(R.string.my_toast_message))
        .inRoot(withDecorView(not(getActivity().getWindow().getDecorView())))
        .check(matches(isDisplayed()));

below you can find the code I'm using to check that a toast message is NOT shown (none of them work):

Approach one:

onView(withText(R.string.error_invalid_login))
        .inRoot(withDecorView(not(getActivity().getWindow().getDecorView())))
        .check(matches(not(isDisplayed())));

Approach two:

onView(withText(R.string.error_invalid_login))
        .inRoot(withDecorView(not(getActivity().getWindow().getDecorView())))
        .check(doesNotExist());

Any idea about how can I check that a toast message is not shown would be really appreciated :)

JulianHarty
  • 3,178
  • 3
  • 32
  • 46
Androider
  • 441
  • 2
  • 6
  • 17
  • Your second approach seems to be correct to me. What kind of unexpected behavior are you getting if you're saying it's not working? – appoll Apr 28 '15 at 09:56
  • The second approach is working for me. As @appoll said, what is the result of running the second approach in a test? – Daniel Apr 28 '15 at 15:12
  • 1
    I get a NoMatchingRootException for your approach two, so its not really working for me either, did you get it to work? – A. Steenbergen Nov 27 '15 at 21:01

9 Answers9

7

The best way to test toast message in espresso is using a custom matcher:

public class ToastMatcher extends TypeSafeMatcher<Root> {
    @Override public void describeTo(Description description) {
        description.appendText("is toast");
    }

    @Override public boolean matchesSafely(Root root) {
        int type = root.getWindowLayoutParams().get().type;
        if ((type == WindowManager.LayoutParams.TYPE_TOAST)) {
            IBinder windowToken = root.getDecorView().getWindowToken();
            IBinder appToken = root.getDecorView().getApplicationWindowToken();
            if (windowToken == appToken) {
                //means this window isn't contained by any other windows. 
            }
        }
        return false;
    }
}

This you can use in your test case:

  1. Test if the Toast Message is Displayed

    onView(withText(R.string.message)).inRoot(new ToastMatcher())
    .check(matches(isDisplayed()));
    
  2. Test if the Toast Message is not Displayed

    onView(withText(R.string.message)).inRoot(new ToastMatcher())
    .check(matches(not(isDisplayed())));
    
  3. Test id the Toast contains specific Text Message

    onView(withText(R.string.message)).inRoot(new ToastMatcher())
    .check(matches(withText("Invalid Name"));
    

I copied this answer from my blog - http://qaautomated.blogspot.in/2016/01/how-to-test-toast-message-using-espresso.html

TWiStErRob
  • 44,762
  • 26
  • 170
  • 254
anuja jain
  • 1,367
  • 13
  • 19
  • 1
    What do you do if you don't have an ID for the Toast? i.e. Say you don't have R.string.message? – Frikster Dec 02 '16 at 14:00
  • You can add the complete text as per displayed in the toast message. – anuja jain Dec 04 '16 at 14:24
  • that's not a good option since throwing and catching `NoMatchingRootException` consumes a lot of time in a comparison with the default test case. Seems that Espresso is waiting for the Root for a while – Beloo Apr 23 '20 at 15:43
  • theres no return true in this example? – glend May 12 '21 at 11:57
6

It is required to catch the case when the toast does not exist, for which a NoMatchingRootException is thrown. Below shows the "Espresso way" of catching that.

public static Matcher<Root> isToast() {
    return new WindowManagerLayoutParamTypeMatcher("is toast", WindowManager.LayoutParams.TYPE_TOAST);
}
public static void assertNoToastIsDisplayed() {
    onView(isRoot())
            .inRoot(isToast())
            .withFailureHandler(new PassMissingRoot())
            .check(matches(not(anything("toast root existed"))))
    ;
}

A quick (self-)test that uses the above:

@Test public void testToastMessage() {
    Toast toast = createToast("Hello Toast!");
    assertNoToastIsDisplayed();
    toast.show();
    onView(withId(android.R.id.message))
            .inRoot(isToast())
            .check(matches(withText(containsStringIgnoringCase("hello"))));
    toast.cancel();
    assertNoToastIsDisplayed();
}

private Toast createToast(final String message) {
    final AtomicReference<Toast> toast = new AtomicReference<>();
    InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
        @SuppressLint("ShowToast") // will be shown later
        @Override public void run() {
            toast.set(Toast.makeText(InstrumentationRegistry.getContext(), message, Toast.LENGTH_LONG));
        }
    });
    return toast.get();
}

The magical reusable helper classes:

public class PassMissingRoot implements FailureHandler {
    private final FailureHandler defaultHandler
            = new DefaultFailureHandler(InstrumentationRegistry.getTargetContext());
    @Override public void handle(Throwable error, Matcher<View> viewMatcher) {
        if (!(error instanceof NoMatchingRootException)) {
            defaultHandler.handle(error, viewMatcher);
        }
    }
}

public class WindowManagerLayoutParamTypeMatcher extends TypeSafeMatcher<Root> {
    private final String description;
    private final int type;
    private final boolean expectedWindowTokenMatch;
    public WindowManagerLayoutParamTypeMatcher(String description, int type) {
        this(description, type, true);
    }
    public WindowManagerLayoutParamTypeMatcher(String description, int type, boolean expectedWindowTokenMatch) {
        this.description = description;
        this.type = type;
        this.expectedWindowTokenMatch = expectedWindowTokenMatch;
    }
    @Override public void describeTo(Description description) {
        description.appendText(this.description);
    }
    @Override public boolean matchesSafely(Root root) {
        if (type == root.getWindowLayoutParams().get().type) {
            IBinder windowToken = root.getDecorView().getWindowToken();
            IBinder appToken = root.getDecorView().getApplicationWindowToken();
            if (windowToken == appToken == expectedWindowTokenMatch) {
                // windowToken == appToken means this window isn't contained by any other windows.
                // if it was a window for an activity, it would have TYPE_BASE_APPLICATION.
                return true;
            }
        }
        return false;
    }
}
TWiStErRob
  • 44,762
  • 26
  • 170
  • 254
  • You can easily add a timeout by adding `if (timeout[0]) throw new RuntimeException("Searched for root until timeout");`at the beginning of `matchesSafely`. Then set `timeout[0]=true` from outside after a few seconds. If you use a custom exception instead, you can catch it in the test case. – DanD Dec 09 '20 at 06:33
  • 1
    is this work on android 11 / 12? – Venkataramanan Jul 21 '22 at 06:46
3

Seems that such simple check is impossible with espresso if you have not only toast but a PopupWindow, for example.

For this case is suggest just to give up with espresso here and use UiAutomator for this assertion

val device: UiDevice
   get() = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())

fun assertPopupIsNotDisplayed() {
    device.waitForIdle()
    assertFalse(device.hasObject(By.text(yourText))))
}

fun assertPopupIsDisplayed() {
    device.waitForIdle()
    assertTrue(device.hasObject(By.text(yourText))))
}
Beloo
  • 9,723
  • 7
  • 40
  • 71
2

This works

boolean exceptionCaptured = false;
try {
  onView(withText(R.string.error_invalid_login))
          .inRoot(withDecorView(not(is(getActivity().getWindow().getDecorView()))))
          .check(doesNotExist());
}catch (NoMatchingRootException e){
  exceptionCaptured = true;
}finally {
  assertTrue(exceptionCaptured);
}
Maragues
  • 37,861
  • 14
  • 95
  • 96
  • as a side note this code can do both exist and does not exist. All we need to do to set boolean flags correctly. Great answer. – A_P Dec 19 '17 at 11:58
  • that's not a good option since throwing and catching NoMatchingRootException consumes a lot of time in a comparison with the default test case. Seems that Espresso is waiting for the Root for a while – Beloo Apr 23 '20 at 15:44
1

In Kotlin you can test Toast by custom class like this

import android.os.IBinder
import android.view.WindowManager
import androidx.test.espresso.Root
import org.hamcrest.Description
import org.hamcrest.TypeSafeMatcher

class ToastMatcher : TypeSafeMatcher<Root?>() {

    override fun matchesSafely(item: Root?): Boolean {
        val type: Int? = item?.windowLayoutParams?.get()?.type
        if (type == WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW) {
            val windowToken: IBinder = item.decorView.windowToken
            val appToken: IBinder = item.decorView.applicationWindowToken
            if (windowToken === appToken) { // means this window isn't contained by any other windows.
                return true
            }
        }
        return false
    }

    override fun describeTo(description: Description?) {
        description?.appendText("is toast")
    }
}

Use it in your test function like this:

onView(withText("Your toast text"))
    .inRoot(ToastMatcher().apply {
        matches(isDisplayed())
    })
Marawan Mamdouh
  • 584
  • 1
  • 6
  • 15
0

I know its late but may be this will help somebody else.

    onView(withText("Test")).inRoot(withDecorView(not(mActivityRule.getActivity().getWindow().getDecorView())))
            .check(doesNotExist());
Shadow Man
  • 21
  • 3
0

Like @anuja jain's answer but if you get the NoMatchingRootException you can comment out the if ((type == WindowManager.LayoutParams.TYPE_TOAST)) check and add the return true; line to the inner if block.

KenIchi
  • 1,129
  • 10
  • 22
-1

Try with below solution

onView(withId(android.R.id.message))
                .inRoot(withDecorView(not(is(mRule.getActivity().getWindow().getDecorView()))))
                .check(matches(withText("Some message")));
NSK
  • 251
  • 3
  • 10
-2

You can look at the source code here and create your own view matcher that does exactly the opposite.

Catalina
  • 1,954
  • 1
  • 17
  • 25
  • 1. link only (already broken), 2. not an answer, the question is not about the opposite, 3. working with non-existent views/roots is not trivial – TWiStErRob Sep 27 '16 at 15:22