Testing request permissions in an Android application
Tags: android, testing, development, uitesting
Published: 2019-07-15 11:12:02

---
title: "Testing request permissions in an Android application"
published: true
description: 
tags: [ "android", "testing", "development", "ui testing" ]
canonical_url: https://eysermans.com/post/testing-request-permissions-in-an-android-application
---

Originally posted on the 15th of july 2019 on my [own blog](https://eysermans.com/post/testing-request-permissions-in-an-android-application).

While working on an Android application I wanted to test permission requests. This application needed the location permission, typically this is code that is written once and when it is marked as done never looked at again. I wanted to create some UI tests to ensure that everything works and will still work in the future as expected.

Let's start this off with creating a sample application. It's written in Kotlin with API 23 as minimum API level and it also uses the androidx.* artifacts.

<img src="https://eysermans.com/images/articles/testing-request-permissions-in-an-android-application/Screenshot_2019-07-13_at_22-4b0a5092-dde1-4210-a438-9b8566148325.07.27.png" alt="" style="width: 75%" />

Following permissions are added in the `AndroidManifest.xml` file:

    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

When the activity is created, the app will check if the location permissions have been granted. If they haven't been granted yet, the app will request them. When the permissions are denied, the app will show `Permissions denied` to the user. When the permissions are granted, it will show `Permissions granted`. Pretty straightforward.

This is the overridden `onCreate` method: 

    override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) 
                   != PackageManager.PERMISSION_GRANTED && 
                ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) 
                   != PackageManager.PERMISSION_GRANTED) {
    
                requestPermissions(
                    arrayOf(
                        Manifest.permission.ACCESS_FINE_LOCATION,
                        Manifest.permission.ACCESS_COARSE_LOCATION
                    ), LocationPermissionRequestCode)
            }
        }

This code checks if the `ACCESS_FINE_LOCATION` and the `ACCESS_COARSE_LOCATION` permissions have been granted. If not they will be requested from the user. The `LocationPermissionRequestCode` variable is a constant to identify the request. In the sample application it is set to `1`.

<img src="https://eysermans.com/images/articles/testing-request-permissions-in-an-android-application/Screenshot_2019-07-13_at_22-1ea4eba7-19ea-4334-bdef-51c1fc7515df.27.16.png" alt="" style="width: 50%" />

Next we need to handle the response of the user. To do this, there is a method which the activity provides that can be overridden. The `onRequestPermissionsResult` method is overridden and a switch statement determines for which request the response is received. Note that the same `LocationPermissionRequestCode` variable is used as above. According to the result of the request the text of the `feedbackLabel` is changed.

    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
    
            when(requestCode) {
                LocationPermissionRequestCode -> {
    
                    if((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
                        feedbackLabel.text = "Permissions granted"
                    } else {
                        feedbackLabel.text = "Permissions denied"
                    }
                    return
                }
                else -> {
    
                }
            }
        }

Note: to access the `feedbackLabel` I'm using the [Kotlin Android Extensions](https://kotlinlang.org/docs/tutorials/android-plugin.html).

<img src="https://eysermans.com/images/articles/testing-request-permissions-in-an-android-application/Screenshot_2019-07-13_at_22-61b9fafd-eaf0-4b38-96d6-6f53fd13e768.33.49.png" alt="" style="width: 50%" />

Now the sample app is working let's get to the testing part. Code like this is often written once, tested once and then never touched again. A developer will grant the permissions and go on with his life. If a bug creeps in it will be noticed too late or not at all. So let's write some UI tests to verify that the app behaves as expected. [UI testing on Android is often done using Espresso](https://medium.com/@smalam119/introduction-to-android-ui-testing-f05a251ab08d) and it works quite well. There even is an [Espresso test recorder built into Android Studio](https://www.thedroidsonroids.com/blog/android/espresso-test-recording) to quickly record tests.

Let's record our first Espresso test, if the user denies the permission the `feedbackLabel` should read `Permissions denied`. In Android Studio select `Run > Record Espresso Test` from the menu. 

<img src="https://eysermans.com/images/articles/testing-request-permissions-in-an-android-application/Screenshot_2019-07-13_at_22-5f7cc541-7df5-4c98-b2dd-85bcf9466246.39.27.png" alt="" style="width: 50%" />

The test recorder will open and a log of all actions that have happened on the device will appear. After clicking the `Deny` button assert that the `feedbackLabel` has the expected value.

<img src="https://eysermans.com/images/articles/testing-request-permissions-in-an-android-application/Screenshot_2019-07-13_at_22-e00c1d8f-81fb-4353-9767-8f75414be00b.40.35.png" alt="" style="width: 50%" />

If Espresso has not been added to the gradle file of the application, Android Studio will propose to do it for you.

<img src="https://eysermans.com/images/articles/testing-request-permissions-in-an-android-application/Screenshot_2019-07-13_at_22-932c51ce-c5aa-4a87-ad3f-77235c3f9d3c.40.50.png" alt="" style="width: 50%" />

Unfortunately Espresso can't be used for this kind of test. [Espresso only works on the current package](https://stackoverflow.com/a/50383212/145585) and the `Allow` and `Deny` buttons from the Permissions dialog are from another package. As you can see in the test record, the click on the `Deny` button is not recorded to the list of actions.

There is however a solution, next to Espresso there is also the [UI Automator testing framework](https://developer.android.com/training/testing/ui-automator). This framework has no problem with accessing elements from another package. We can't use the test recorder so the test will have to be coded but this is pretty straightforward. This line in the gradle file will add UI Automator:

    androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3'

And the test becomes:

    @RunWith(AndroidJUnit4::class)
    class MainActivityTests {
    
        private var device : UiDevice? = null
    
        @get:Rule
        var mainActivityTestRule = ActivityTestRule(MainActivity::class.java)
    
        @Before
        fun setUp() {
            this.device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
        }
    
        @Test
        fun testFeedbackLocationPermissionDenied() {
            val denyButton = this.device?.findObject(UiSelector().text("DENY"))
            val permissionDeniedMessage = this.device?.findObject(UiSelector().text("Permission denied"))
    
            denyButton!!.click()
    
            assert(permissionDeniedMessage!!.exists())
        }
    
        @Test
        fun testFeedbackLocationPermissionAllowed() {
            val allowButton = this.device?.findObject(UiSelector().text("ALLOW"))
            var permissionAllowedMessage = this.device?.findObject(UiSelector().text("Permission allowed"))
            allowButton!!.click()
            assert(permissionAllowedMessage!!.exists())
        }
    }

The `testFeedbackLocationPermissionAllowed` test will check the message when the permission is allowed, the `testFeedbackLocationPermissionDenied` will check the message when the permission is denied. 

There is however a problem with these tests. Once the permission has been allowed, the tests will fail when they are run again. If you think about it, it's not surprising. Because the permission is already granted, the permission popup will no longer be shown the second, third, fourth, ... time the tests are run.

To fix this the granted permissions need to be cleared every time the tests are run. [Clearing the permission can be done using `pm revoke`.](https://stackoverflow.com/questions/43462172/android-revoke-permission-at-start-of-each-test) These commands are added to the `After` method to clean up the permissions.

    @After
    fun tearDown() {
       InstrumentationRegistry.getInstrumentation().uiAutomation.
           executeShellCommand("pm revoke ${InstrumentationRegistry.getInstrumentation().targetContext.packageName} android.permission.ACCESS_COARSE_LOCATION")
    
       InstrumentationRegistry.getInstrumentation().uiAutomation.
           executeShellCommand("pm revoke ${InstrumentationRegistry.getInstrumentation().targetContext.packageName} android.permission.ACCESS_FINE_LOCATION")
        }
    }

When the `tearDown` executes logcat logs the following:

    2019-07-15 11:09:43.639 21138-21154/com.eysermans.permissionuitesting W/UiAutomation: UiAutomation.revokeRuntimePermission() is more robust and should be used instead of 'pm revoke'

So instead of using `pm revoke` we should use [`revokeRuntimePermission`](https://developer.android.com/reference/kotlin/android/app/UiAutomation#revokeRuntimePermission%28kotlin.String,%20kotlin.String%29).

    @After
    fun tearDown() {
        InstrumentationRegistry.getInstrumentation().uiAutomation.revokeRuntimePermission(
            InstrumentationRegistry.getInstrumentation().targetContext.packageName,
            Manifest.permission.ACCESS_COARSE_LOCATION)
    
        InstrumentationRegistry.getInstrumentation().uiAutomation.revokeRuntimePermission(
            InstrumentationRegistry.getInstrumentation().targetContext.packageName,
            Manifest.permission.ACCESS_FINE_LOCATION)
    }

Unfortunately this didn't work either. Every time the `testFeedbackLocationPermissionAllowed` test ran it failed stating that more information can be found in logcat. However I did not find any additional error logging in logcat. It just did not work.

    Test failed to run to completion. Reason: 'Instrumentation run failed due to 'Process crashed.''. Check device logcat for details

Let's explore another option, using the [Android Test Orchestrator](https://developer.android.com/training/testing/junit-runner#using-android-test-orchestrator). Add the dependency to the gradle file:

    androidTestUtil 'androidx.test:orchestrator:1.2.0'

Then add the following line to the `defaultConfig` section of the gradle file:

    testInstrumentationRunnerArguments clearPackageData: 'true

And add the `testOptions` section to the `android` section:

    testOptions {
        execution 'ANDROIDX_TEST_ORCHESTRATOR'
    
        unitTests {
            includeAndroidResources = true
        }
    }

The gradle file now looks like this:

    apply plugin: 'com.android.application'
    
    apply plugin: 'kotlin-android'
    
    apply plugin: 'kotlin-android-extensions'
    
    android {
        compileSdkVersion 28
        defaultConfig {
            applicationId "com.eysermans.permissionuitesting"
            minSdkVersion 23
            targetSdkVersion 28
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
            testInstrumentationRunnerArguments clearPackageData: 'true'
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
    
        testOptions {
            execution 'ANDROIDX_TEST_ORCHESTRATOR'
    
            unitTests {
                includeAndroidResources = true
            }
        }
    }
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
        implementation 'androidx.appcompat:appcompat:1.0.2'
        implementation 'androidx.core:core-ktx:1.0.2'
        implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'androidx.test:runner:1.2.0'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
        androidTestImplementation 'androidx.test:rules:1.3.0-alpha01'
        androidTestImplementation 'com.android.support.test.uiautomator:uiautomator-v18:2.1.3'
    
        androidTestImplementation 'androidx.test.ext:junit:1.1.1'
        androidTestUtil 'androidx.test:orchestrator:1.2.0'
    }

The `tearDown` method can now completely be removed and the `testFeedbackLocationPermissionAllowed` method can now be run over and over again. 

It took some work but this solution works. The permissions are cleared before every test. I preferred using a `tearDown` method because it is more straightforward. But unfortunately it did not work as expected.

[The sample application is available on GitHub.](https://github.com/petereysermans/android-permission-ui-testing)

More links on the subject:

- [Testing Runtime Permissions: Lessons Learned](https://blog.egorand.me/testing-runtime-permissions-lessons-learned/)
- [Android revoke permission at start of each test](https://stackoverflow.com/questions/43462172/android-revoke-permission-at-start-of-each-test)
- [Android Test Orchestrator unmasked](https://medium.com/stepstone-tech/android-test-orchestrator-unmasked-83b8879928fa)
- [Enable the Android Test Orchestrator](https://developer.android.com/training/testing/junit-runner#ato-gradle)