Transcripted Summary

Testing Intents

Espresso-Intents is an extension of Espresso which enables validation and stubbing of intents sent by the application under test.

It is similar to Mockito but for Android Intents.

If your app implicitly delegates functionality to other apps, Espresso-Intents allows you to focus on your own app's logic while assuming that other apps or platforms will function correctly.

Espresso-Intents can be used to match and validate outgoing intents or even provide stub responses in place of actual intent responses.


Testing Intent (Demo)

In this demo we will learn how to use Intents with Espresso.

Begin by running the "IntentsBasicSample" sample application and explore the interface. In this application we are simulating dialer activities.

We can enter a phone number, then click the "CALL NUMBER" button to call that number (the "PICK NUMBER" button will not be used in this demo).

Using Intents we can verify that the dialer was opened without actually opening the activity.



Before we begin, check to see that the Espresso dependencies have already been added to the "build.gradle" file.

This particular demo requires adding a new dependency for Intents.



Download Dependencies

Dependencies can be downloaded from repositories such as the Maven Central Repository or from alternatives like mvnrepository.


Before writing our test case let's examine our application activities. Under the "main" package we find two activities, ContactsActivity and DialerActivity.

Inspecting the DialerActivity source we find several method implementations simulating various dialer activities. Pay particular attention to createCallIntentFromNumber() which populates an Intent object based on the telephone number entered.

Method onActivityResult() asserts activity return codes against enumerated variables like RESULT_OK and RESULT_CANCELLED.

Now let’s begin writing our test case.

Under "androidTest", right-click the package "com.example.android.testing.espresso.IntentsBasicSample" and select "New > Java Class".

Call this new class DialerActivityClass and click "OK".



As in previous demos, add the annotation @RunWith() at the class level to run our test with AndroidJUnit4.class which should already be added to the "build.gradle" file.


@RunWith(AndroidJUnit4.class)
public class DialerActivityTest {

}

Now we will add our activity test rule with the annotation @Rule.

Here, we need to use the activity test rule for intents (IntentsTestRule) which is different from previous examples that used ActivityTestRule.


    @Rule
    public IntentsTestRule<DialerActivity> myIntent = 
    new IntentsTestRule<>(DialerActivity.class);

We also need to define a string to store a phone number entered into the text box and assert whether or not that number is sent with the URL.


private static final String phoneNumber = "123-345-6789";

Now create the test method using the @Test annotation from TestNG.


    @Test
    public void type_number_call()
    {

    }

Before continuing, we need to retrieve some element properties from the application so that we may reference these elements in our code.

Switch to the running device emulator and open the Layout Inspector from the Tools menu. Select the application on the device emulator and click "OK".

Locate the ID of the text box that phone numbers will be entered into from the Properties Table and copy that reference to the code ("id/edit_text_caller_number").

We will also need to retrieve the "CALL NUMBER" button identifier ("id/button_call_number").

Now we can continue our implementation. Don’t forget to import methods as needed. First, type the phone number into the text field.


    @Test
    public void type_number_call() {
        onView(withId(R.id.edit_text_caller_number))
            .perform(typeText(phoneNumber));
        closeSoftKeyboard();
    }

After that we need to click on the "CALL NUMBER" button.


    @Test
    public void type_number_call() {
        onView(withId(R.id.edit_text_caller_number))
            .perform(typeText(phoneNumber));
        closeSoftKeyboard();
        onView(withId(R.id.button_call_number)).perform(click());
    }

Now we need to verify that the activity for the dialer is displayed without actually displaying the activity. If we refer back to the DialerActivity code we will find something called Intent.ACTION_CALL which represents a specific action we can assert against.

If you click on ACTION_CALL to jump to its reference you will see that this action performs a call to an emergency number as specified by the data.

Essentially we need to mock this intent action and verify that ACTION_CALL is invoked without actually displaying it.

We need to create a Uri for our test that begins with the string "tel:" and appends the telephone number used in our test case.


private static final Uri IntentPhoneNumber = Uri.parse("tel:" + phoneNumber);

Let’s finish implementing our test.

We want to assert that the intent includes ACTION_CALL.

The data that we need to pass is the intended phone number, which includes the Uri and our phone numbers that we entered in the text box.


    @Test
    public void type_number_call() {
        onView(withId(R.id.edit_text_caller_number))
            .perform(typeText(phoneNumber));
        closeSoftKeyboard();

        onView(withId(R.id.button_call_number)).perform(click());

        intended(allOf(hasAction(Intent.ACTION_CALL)
            ,hasData(IntentPhoneNumber)));
    }

Now let's try to run our test case and observe the behavior of our test script. The test passes despite the app displaying the dialer activity still.



We still have one step to complete in order to finish our test case.

Before running our test we need to add a check to see if we are using an external intent. We will return the activity result code RESULT_OK.

After this we can verify that the application behaves correctly without opening the dialer activity.


    @Rule
    //
    //
    //

    @Before
    public void stubAllExternalIntents() 
    {
        intending(not(isInternal()))
        .respondWith(new
            Instrumentation
                .ActivityResult(Activity.RESULT_OK, null));
    }

    @Test
    //
    //
    //

If isInternal() returns false, we need to check that the result of the instrumentation response contains Activity.RESULT_OK.

The result data is null because we don't need to use any result data.

Now let’s run our test again. Be sure to close the previous test run if it is still active.



Now our test is complete with mocking, stubbing, and Intents.


Code


package com.example.android.testing.espresso.IntentsBasicSampl;

import static android.app.Instrumentation.ActivityResult;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.action.ViewActions.closeSoftKeyboard;
import static androidx.test.espresso.action.ViewActions.typeText;
import static androidx.test.espresso.intent.Intents.intended;
import static androidx.test.espresso.intent.Intents.intending;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasData;
import static androidx.test.espresso.intent.matcher.IntentMatchers.isInternal;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.core.AllOf.allOf;

import android.app.Activity;
import android.content.Intent;
import android.net.Uri;

import com.example.android.testing.espresso.IntentsBasicSample.DialerActivity;
import com.example.android.testing.espresso.IntentsBasicSample.R;

import androidx.test.espresso.intent.rule.IntentsTestRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(AndroidJUnit4.class)
@LargeTest
public class DialerActivityTest {

    private static final String phoneNumber = "123-345-6789";
    private static final Uri IntentPhoneNumber = Uri.parse("tel:" + phoneNumber);

    @Rule
    public IntentsTestRule<DialerActivity> mActivityRule = new IntentsTestRule<>(DialerActivity.class);

    @Before
    public void stubAllExternalIntents() {
        intending(not(isInternal()))
            .respondWith(new ActivityResult(Activity.RESULT_OK, null));
    }

    @Test
    public void typeNumber_ValidInput_InitiatesCall() {
        onView(withId(R.id.edit_text_caller_number))
            .perform(typeText(phoneNumber), closeSoftKeyboard());
        onView(withId(R.id.button_call_number)).perform(click());

        intended(allOf(
            hasAction(Intent.ACTION_CALL),
        	hasData(IntentPhoneNumber)));
    }
}


Resources



© 2021 Applitools. All rights reserved. Terms and Conditions Privacy Policy GDPR