Handling Android runtime permissions in UI tests
Several months ago I took a look at the new Android Permissions model where things were changing quite a lot with the introduction of Marshmallow. the implementation of this new model meant that we needed to ensure that:
- We checked if we had the permission we needed when it was required
- We requested the permission from the user when it deemed appropriate
- Correctly handled the request (empty states or data feedback) within the UI to represent the outcome of being granted or denied the required permission
So now we’ve implemented all of the above, it looks and behaves as expected on both Marshmallow and pre-Marshmallow devices, right? 👹
Now, I’m sure you’ve written all of your functional tests like a good developer. But wait, what about tests involving screens where we require a permission, aren’t these going to fail on 6.0+ devices? And how are we meant to test the different UI states when being granted and denied a permission?!
I had a little poke online and in several places developers were suggesting granting and revoking permissions using adb. However, this isn’t a good approach if we want to test the actual interaction with the UI of our application - so to me this kind of defeats the point of these tests.
So, can we use Espresso to do this? Unfortunately, Espresso doesn’t have the ability to access components from outside of our application’s package.
Luckily, the Testing Support Library has another framework we can use - UIAutomator! Unlike espresso, UIAutomator can interact with system applications which means that we’ll be able to interact with the Permissions dialog if needed. Even better, UIAutomator can be used alongside espresso for your UI tests.
But how does this work? Well, the framework provides a UiDevice class which can be used to both access and perform operations on the device which the app is running on. We can also use it to simulate user actions on the device, which can be useful for events such as carrying out D-pad actions or interacting with the notification drawer.
So let’s take a look at an example test flow for a screen that displays a list of the users contacts from their device:
- We begin by launching our test activity.
- When the activity launches, on 6.0+ our application code triggers a request for access to our contacts. Since we haven’t granted this permission, we’re shown a dialog to grant permission.
- Next, we need to either grant or deny this permission by using UiAutomator to interact with the permissions dialog. If we grant this permission by clicking on “Allow” then we next need to check that the contacts are correctly displayed in the list. However, if we deny this permission then we need to check that our UI displays the correct empty state to reflect this behaviour.
So how do we do this? Well, if we want to grant a permission in our UI test then we need to find the corresponding UiObject that we wish to click on. This is object is a representation of a view - it’s not bound to our view but contains information to locate the matching view at runtime, based on the properties of the UiSelector instance within it’s constructor. A UiSelector instance is an object which declares elements within our layout to be targeted by our test. We can set various properties for this UiSelector instance such as a text value, class name or content-description.
So now that we have the UiObject that we wish to interact with in our tests, we can make use of one of it’s many methods — a simple click(). All of this together can be achieved by the implementation below:
Denying permission would also be approached similarly, we’d just need to look for the appropriate “Deny” text rather than “Allow”.
Using the above approach, you’ll now be able to both grant and deny permissions in your tests on 6.0+ devices where your application requires them to test that the UI responds accordingly. Obviously a downside here is the “Allow” and/or “Deny” text being changed in the system, which would break the tests.
Alongside this, the UIAutomator framework has a lot to offer, so it’s really worth checking out the documentation to see how else you can utilise it in your testing. You never know, it may save you a lot of time one day.
How are you currently approaching this in your tests? I’d love to hear of your approaches!
The latest Tweets from Joe Birch (@hitherejoe). Android, photography, bass and a lot of running. Android Developer…www.twitter.com
Check out more of my projects at hitherejoe.com