Checking Toast Message in Android Espresso

Checking toast message in android espresso

This slightly long statement works for me:

import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
....
onView(withText(R.string.TOAST_STRING)).inRoot(withDecorView(not(is(getActivity().getWindow().getDecorView())))).check(matches(isDisplayed()));

How to test a Toast in a fragment

Use the RobolectricTestRunner to launch a fragment in a test Activity. To launch the fragment create a FragmentScenario in a @Before function. In a @Test fun use ShadowToast to test the toast.

@RunWith(RobolecticTestRunner::class)
class MyFragmentUnitTest {

private lateinit var scenario: FragmentScenario<MyFragment>

@Before
fun setup() {

val viewModel: MyViewModel = mock(MyViewModel::class.java)

// Your scenario may not need to include all this information.
scenario = launchFragmentInContainer(
factory = MyFragmentFactory(viewModel),
fragmentArgs = null,
themeResId = R.style.Theme_mine
)
}

@Test
fun `show toast`() {
// Reference the button that will display the toast.
val toastButton = onView(ViewMatchers.withId(R.id.button_toast))

// perform a click on the button
toastButton.perform(ViewActionsclick())

val toastText = "This is my toast"
assertEquals(ShadowToast.shoedToast(toastText))
}
}

Espresso checking if toasts are displayed (one on top of another)

The NoMatchingViewException you are seeing means your ToastMatcher does find a root view with TYPE_TOAST, but cannot find the requested view inside that root (otherwise, you'd get a NoMatchingRootException).

I guess the reason is that Android is not showing toasts on top of each other, but one after the other. Thus, probably the only view it finds in a toast-root is your first toast (your second toast has not yet been shown). Thus, before checking for the second toast you will have to somehow wait until your first toast has disappeared. This isn't trivial, unfortunately (see below), and I believe you cannot get around changing production code.

A possible solution is given in https://stackoverflow.com/a/32023568/1059766. The basic idea is to attach a OnAttachStateChangedListener to your toast's view before showing the toast, and use that listener to track when the view is attached to and detached from the view hierarchy. This can then be used to implement a custom IdlingResource that can wait for a toast to disappear.

The way espresso waits for things is by means of IdlingResources. I currently cannot see how you could create a custom idling resource to wait for a toast without changing production code. Therefore, something along the lines of the aformentioned answer is the best I can think of, even though the required change to production code is not very appealing.

That being said, note that your ToastMatcher solution (that's often suggested on stackoverflow and blogs) is also not a really reliable way to test toasts. It works in most situations, but not always. Consider e.g. the following snippet:

new AsyncTask<Void, Void, Void>() {
public void doInBackground(...) {
// start background work for 10s (or just Thread.sleep(10000))
}
}.execute()
Toast.make(context, R.string.mytoast, Toast.LENGTH_SHORT).show()

As espresso always waits until the UI thread and all async-tasks are idle, it will in the above example wait for (about) 10s until the isDisplayed() check is performed. But at that point the toast will have disappeared and therefore the check fails. I hope this is enough to illustrate the inherent problem with this approach. The following statement from Valera Zakharov in https://groups.google.com/d/msg/android-test-kit-discuss/uaHdXuVm-Bw/cuQASd3PdpgJ seems to confirm that there is no easy solution to test toasts with espresso:

Short answer: Unfortunately, there is no thread-safe way of doing this in Android, so we don't provide this capability in Espresso.

Details:
The way Toasts are implemented makes it possible to detect a toast has been displayed. However there is no way to see if a Toast has been requested, thru a call to show()) or to block between the period of time between show() and when the toast has become visible. This is opens up unresolvable timing issues (that you can only address thru sleep & hope) [...].

Zakharov then also suggests to add some hooks to the production code (as far as I understand that). Thus, I guess adding an IdlingResource based on some production code hooks is really the best you can do (this might also make your toast testing more stable in general, as you can then test your toasts as outlined by Zakharov).



Related Topics



Leave a reply



Submit