Hello, espresso! Part 3 Working with intents 🔄

Gaurav Singh
6 min readMay 11, 2022

--

Things are getting pretty exciting 😁 as we work our way through learning espresso’s API

In the last part Hello, espresso! Part 2 Working with lists, we learned how to work with list controls ( RecyclerView, AdapterView) in espresso. Go ahead and have a read in case you missed it.

Learning to test Intents 💪

In this post, we’ll understand how to automate testing of intents using espresso.

espresso-intents provides us the capabilities to validate intents for a couple of important use cases:

  • Test whether the correct intent is invoked with valid data by our app
  • Or, even stub out the intents sent out so that we can verify only our apps logic (while assuming other apps that we depend upon have been tested independently)

What is an Android intent?

In developer.android.com’s post about Android Intents

An intent is an abstract description of an operation to be performed.

It can be used:

  • with startActivity to launch an Activity,
  • broadcastIntent to send it to any interested BroadcastReceiver components,
  • and Context.startService(Intent) orContext.bindService(Intent, ServiceConnection, int) to communicate with a background Service.

One of the use cases for which Intents are used a lot is to launch Activities and they can be thought is a data structure that holds an abstract description of an action to be performed

There are 2 components to an Intent:

  • Action: What action has to be performed, some examples of this are:
  • ACTION_VIEW displays data to the user, for instance, if we use it astel: URI it will invoke the dialer (we'll see this in our test example),
  • ACTION_EDIT gives explicit edit access to given data
  • Data: Data to operate on expressed as aUri

You can read the full doc here to understand more about Intents

Understanding our App under test 🕵🏻

Let’s start by understanding the app under test

We’ll use IntentsBasicSample app, which has an EditText to enter a phone no and a Button to either call a number or randomly pick a no, if the user taps on the call number button it launches a dialer app

The below scenarios are possible

I’ve written this in Gherkin syntax for clarity, however in a live test, the tests should always describe behavior and no be as procedural as i’ve written below. Read this page on cucumber website to understand more

Let’s use Layout inspector to grab the selectors for the elements we want to work with:

Add required dependencies

We need to add espresso-intents dependency to our app/build.gradle file as below, also it's only compatible with espresso 2.1+ and android testing lib version 0.3+, so we need to double-check their versions in our dependencies as well

Test to launch a dialer activity using intents and validation

Below is the complete test to perform our scenario, don’t worry 🧘🏻‍♂️ if it does not make sense right now, we’ll unpack this in detail below, the complete example is mentioned so that you can skim through its structure first before we dive deeper

Setting up intents and permissions

  • Just like Views, we'll use JUnit rules to set up and tear down our intents before and after each test. We'll useActivityScenarioRule for this
  • Since we want to automate the DialerActivity class, we'll pass that as the generic type within<>

Note: If you read other blogs and even the official google guide on espresso-intents, they show usage ofIntentsTestRule for setting up intents, but this has recently been deprecated with suggestion to use ActivityScenarioRule with init() and release(). I'm sure these examples and docs would get updated soon, you can meanwhile refer to this blog 😉. You can see this commit to understand how your test code would look like prior and after this change.

Writing core test logic

With that taken care of let’s write our test

We store a test number in a static variable

We’ll type the phone no into our EditText as below and then close the keyboard:

We’ll then tap the call number button

Asserting our intent was fired

Great 🙌🏼, we want to verify that our Intent was actually invoked and we can achieve that by using intended method that takes an Intent matcher (either an existing one or one that we define).

Tip: 💡 You can refer to Hamcrest Tutorial understand how hamcrest matchers work since we are going to use them heavily with espresso

If you notice, we use allOf() matcher, that checks that the examined object matches all of the specified matchers

We first check that the intent had the correct action by calling hasAction(Intent.ACTION_CALL)

How do we know which action to assert? 🤔

We can look into app source in DialerActivity to understand more details about our intent

If you look at createCallIntentFromNumber method, you can see we create an intent with action Intent.ACTION_CALL:

Also, we see that we set a phone no as the intents data in:

Here is the full method for reference

We also assert that our intent has the correct phone no set as data by:

Preparing the phone no Uri earlier

and then add the below line in our allOf matcher

Stubbing intent response

If you run this test, you’ll see the Dialer Activity pop up

In the above test, we saw how espresso intents could launch another activity and we can quickly validate them using intended,

However, If we only care about testing the logic of our app and not so much about a 3rd party apps logic (since we anyways cannot manipulate the UI of external activity, nor control the ActivityResult returned to the activity we are testing), then espresso allows us to stub intents and returns a mock response as well using intending

Let’s see how we can do this:

We add the below line in our @Before annotated setup method:

Let’s understand its nuts and bolts:

  • We can configure espresso to return a RESULT_OK for any intent call by usingisInternal() intent matcher that checks if an intents package is the same as the target package for the instrumentation test.
  • We then ask espresso to return a RESULT_OK as a stubbed response by usingrespondWith() and mention the result we want to return:

Here:

  • Activity.RESULT_OK is the resultCode and
  • null is the resultData since we don't want to return anything in the intent response

If we rerun the above test, you’ll see that no dialer activity is started since the intent call to external activities is going to be stubbed

Test our own apps intent without making an external activity call a.k.a Stubbing response

Let’s see another example of stubbing using intending updated functional test flow

We can write the below test to achieve this flow:

In this example, we show that we could also selectively stub out intent calls to a particular activity, e.g. if we wanted all calls to ContactsActivity to return a code: RESULT_OK and a valid phone no, we can do so by writing:

Note: If we want to stub calls to all classes in a package we could use: toPackage method inside intending

Here we use hasComponent(hasShortClassName(".ContactsActivity")) that matches any call to class ContactsActivity and respond with RESULT_OK, also we return resultData as the return value of createResultData method

If we see impl of createResultData in ContactsActivity source code, we see it returns an empty intent with a phone no value

We finally tap on the pick number button and verify that the EditText button has the same no as the one returned by the stubbed intent call

And that’s how you automate intents with espresso! ✅

Resources

  • You can find the app and test code for this post on Github:
  • Please read espresso-intents that talks about how to work with intents on Android developers
  • Refer to original source code on testing-samples

Conclusion

Hopefully, this post gives you an idea of how to work with intents in espresso. Stay tuned for the next post where we’ll dive into how to automate and work with idling resources with espresso

As always, Do share this with your friends or colleagues and if you have thoughts or feedback, I’d be more than happy to chat over on Twitter or comments. Until next time. Happy Testing and learning.

Originally published at https://automationhacks.io on May 11, 2022.

--

--

Gaurav Singh
Gaurav Singh

Written by Gaurav Singh

Principal SDET @ CRED, ex Meta, Gojek | 13+ years of hands-on solving deep testing problems and leading engineering teams

No responses yet