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 :)
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;
}
}