3

I have an activity that can display different toasts depending on the status returned from a web service call.

I am writing a test class, and my first test tests that one of these toasts is displayed when there's a network error. Below is the test class:

@RunWith(AndroidJUnit4.class)
public class LoginActivityTest extends BaseTest {

    @Rule
    public final ActivityTestRule<LoginActivity> login = new ActivityTestRule(LoginActivity.class, true);

    @Test
    public void noNetwork() {
        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
            @Override
            public void run() {
                login.getActivity().onEventMainThread(new LoginResp(CopiaWebServiceClient.ResponseStatus.NETWORK_ERROR));
            }
        });

        onView(withText(R.string.toast_no_network_connection))
            .inRoot(withDecorView(not(is(login.getActivity().getWindow().getDecorView()))))
            .check(matches(isDisplayed()));
    }
}

So the noNetwork test calls LoginActivity's onEventMainThread(LoginResp loginResp) method (this needs to run on the UI thread, hence my use of runOnMainSync) with a network error status to prompt it to show the expected toast_no_network_connection toast.

This test works and runs and passes successfully.

This is my issue:

If I add a second test to the test class, the second test fails. This is the second test, identical to the first except that it passes a different error status to onEventMainThread(LoginResp loginResp) so that a different toast will be shown:

    @Test
    public void serverErr() {
       InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
            @Override
            public void run() {
                login.getActivity().onEventMainThread(new LoginResp(CopiaWebServiceClient.ResponseStatus.HTTP_SERVER_ERROR));
            }
        });

        onView(withText(R.string.toast_operation_failure_app_error))
            .inRoot(withDecorView(not(is(login.getActivity().getWindow().getDecorView()))))
            .check(matches(isDisplayed()));
    }

The second test fails with output: android.support.test.espresso.NoMatchingViewException: No views in hierarchy found matching: with string from resource id: <2131689669>[toast_operation_failure_app_error] value: Sorry, failure to complete operation due to application error.

However, watching the emulator while running the tests I see both the initial toast_no_network_connection toast (expected by the first test), and then the toast_operation_failure_app_error toast (expected by the second test). Why does the second test fail?

It has something to do with the tests running one after the other, because when I comment out the first test, the second test passes.

My onEventMainThread(LoginResp loginResp) method in LoginActivity has the following code that shows an appropriate toast based on status:

switch (status) {

        case HTTP_UNAUTHORIZED:
            DialogCreator.createAlertDialog(this, getString(R.string.dialog_msg_login_fail)).show();

            break;

        case NETWORK_ERROR:
            Toast.makeText(this, getString(R.string.toast_no_network_connection), Toast.LENGTH_SHORT).show();

            break;

        default:
            Toast.makeText(this, getString(R.string.toast_operation_failure_app_error), Toast.LENGTH_SHORT).show();

    }

Debugging the test I see that the first test enters the NETWORK_ERROR case, as expected, and the second test enters the switch statement's default section, also as expected.

user1310850
  • 87
  • 2
  • 6
  • "It has something to do with the tests running one after the other, because when I comment out the first test, the second test passes" -- have you tried reversing the order of the tests? IOW, is it always the second test that fails or is it always `R.string.toast_operation_failure_app_error` that fails? I have never Espresso-tested `Toasts` (I'm surprised that it's possible, as the `View` isn't strictly tied to your UI, last I checked) – CommonsWare Dec 03 '17 at 12:26
  • Yes, I have reversed the order of the tests and found that the second test always fails, so toast_no_network_connection will fail if it comes second. – user1310850 Dec 04 '17 at 16:51
  • Hmmm... not sure what you can do. You might try consolidating these into a single `@Test` method and see what results you get. I'm not sure why the second activity instance would have a problem where the first one did not, but using a single `@Test` method for these would have you use a single activity instance, which might get you past the problem. – CommonsWare Dec 04 '17 at 16:58
  • Yes, I have reversed the order of the tests and found that the second test always fails, so toast_no_network_connection will fail if it comes second. When the _second_ test fails, the view hierarchy included in the `NoMatchingViewException` message for the failure contains the `Toast` from the _first_ test. So the first test's `Toast` seems to "hang around", so to speak, after it has ran. – user1310850 Dec 04 '17 at 17:25
  • "So the first test's Toast seems to "hang around", so to speak, after it has ran" -- it will hang around for `Toast.LENGTH_SHORT` amount of time. Your second `Toast` will not be displayed until then. – CommonsWare Dec 04 '17 at 17:27
  • Unfortunately the results are the same, i.e. the second check for an expected `Toast` fails, when the test code from `serverErr` is moved below the existing code in the noNetwork method to produce a single `@Test`. 'Found some seemingly related discussion here https://stackoverflow.com/questions/38329114/espresso-checking-if-toasts-are-displayed-one-on-top-of-another – user1310850 Dec 04 '17 at 19:11
  • Add a `sleep()` to tie up the test until the `Toast` period is over. Or, see if there is some `IdlingResource` that can detect when the first `Toast` goes away, so you can block waiting until "the coast is clear" before proceeding. – CommonsWare Dec 04 '17 at 19:14
  • Yes, the `sleep()` works, allowing my test to wait until the `Toast.LENGTH_SHORT` period is over. The second test is now passing. – user1310850 Dec 04 '17 at 19:48

1 Answers1

0

After some further playing with the tests I opted for an approach suggested by Eirik W's answer from Espresso checking if toasts are displayed (one on top of another)

I changed my production code slightly to add a @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) annotated Toast member to LoginActivity to enable an @After annotated method in LoginActivityTest cancel any showing toast after each test.

user1310850
  • 87
  • 2
  • 6