Android Marshmallow: Test permissions with Espresso?

AndroidPermissionsAndroid EspressoAndroid 6.0-Marshmallow

Android Problem Overview


The new permissions scheme introduced by Android Marshmallow requires checking for specific permissions at runtime, which implies the need to provide different flows depending on whether the user denies or allows access.

As we use Espresso to run automated UI tests on our app, how can we mock or update the state of the permissions in order to test different scenarios?

Android Solutions


Solution 1 - Android

With the new release of the Android Testing Support Library 1.0, there's a GrantPermissionRule that you can use in your tests to grant a permission before starting any tests.

@Rule public GrantPermissionRule permissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION);

Kotlin solution

@get:Rule var permissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION)

@get:Rule must be used in order to avoid java.lang.Exception: The @Rule 'permissionRule' must be public. More info here.

Solution 2 - Android

The accepted answer doesn't actually test the permissions dialog; it just bypasses it. So, if the permissions dialog fails for some reason, your test will give a false green. I encourage actually clicking the "give permissions" button to test the whole app behaviour.

Have a look at this solution:

public static void allowPermissionsIfNeeded(String permissionNeeded) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !hasNeededPermission(permissionNeeded)) {
sleep(PERMISSIONS_DIALOG_DELAY);
UiDevice device = UiDevice.getInstance(getInstrumentation());
UiObject allowPermissions = device.findObject(new UiSelector()
.clickable(true)
.checkable(false)
.index(GRANT_BUTTON_INDEX));
if (allowPermissions.exists()) {
allowPermissions.click();
}
}
} catch (UiObjectNotFoundException e) {
System.out.println("There is no permissions dialog to interact with");
}
}

Find the whole class here: https://gist.github.com/rocboronat/65b1187a9fca9eabfebb5121d818a3c4

By the way, as this answer has been a popular one, we added PermissionGranter to Barista, our tool above Espresso and UiAutomator to make instrumental tests green: https://github.com/SchibstedSpain/Barista check it out, because we will maintain it release by release.

Solution 3 - Android

Give a try with such static method when your phone is on English locale:

private static void allowPermissionsIfNeeded() {
    if (Build.VERSION.SDK_INT >= 23) {
        UiDevice device = UiDevice.getInstance(getInstrumentation());
        UiObject allowPermissions = device.findObject(new UiSelector().text("Allow"));
        if (allowPermissions.exists()) {
            try {
                allowPermissions.click();
            } catch (UiObjectNotFoundException e) {
                Timber.e(e, "There is no permissions dialog to interact with ");
            }
        }
    }
}

I found it here

Solution 4 - Android

You can grant permissions before the test is run with something like:

@Before
public void grantPhonePermission() {
    // In M+, trying to call a number will trigger a runtime dialog. Make sure
    // the permission is granted before running this test.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        getInstrumentation().getUiAutomation().executeShellCommand(
                "pm grant " + getTargetContext().getPackageName()
                        + " android.permission.CALL_PHONE");
    }
}

But you can't revoke. If you try pm reset-permissions or pm revoke... the process is killed.

Solution 5 - Android

Actually there are 2 ways of doing this I know so far:

  1. Grant the permission using adb command before test starts (documentation):

adb shell pm grant "com.your.package" android.permission.your_permission

  1. You can click on permission dialog and set the permission using UIAutomator (documentation). If your tests are written with Espresso for android you can combine Espresso and UIAutomator steps in one test easily.

Solution 6 - Android

You can achieve this easily by granting permission before starting the test. For example if you are supposed to use camera during the test run, you can grant permission as follows

@Before
public void grantPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        getInstrumentation().getUiAutomation().executeShellCommand(
                "pm grant " + getTargetContext().getPackageName()
                        + " android.permission.CAMERA");
    }
}

Solution 7 - Android

ESPRESSO UPDATE

> This single line of code grants every permission listed as parameter > in the grant method with immediate effect. In other words, the app > will be treated like if the permissions were already granted - no more > dialogs

@Rule @JvmField
val grantPermissionRule: GrantPermissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION)

and gradle

dependencies {
  ...
  testImplementation "junit:junit:4.12"
  androidTestImplementation "com.android.support.test:runner:1.0.0"
  androidTestImplementation "com.android.support.test.espresso:espresso-core:3.0.0"
  ...
}

reference: https://www.kotlindevelopment.com/runtime-permissions-espresso-done-right/

Solution 8 - Android

If you need to set a permission for a single test or during runtime rather than a rule you can use this:

PermissionRequester().apply {
    addPermissions(android.Manifest.permission.RECORD_AUDIO)
    requestPermissions()
}

e.g.

@Test
fun openWithGrantedPermission_NavigatesHome() {
    launchFragmentInContainer<PermissionsFragment>().onFragment {
        setViewNavController(it.requireView(), mockNavController)
        PermissionRequester().apply {
            addPermissions(android.Manifest.permission.RECORD_AUDIO)
            requestPermissions()
        }
    }

    verify {
        mockNavController.navigate(R.id.action_permissionsFragment_to_homeFragment)
    }
}

Solution 9 - Android

There is GrantPermissionRule in Android Testing Support Library, that you can use in your tests to grant a permission before starting any tests.

@Rule public GrantPermissionRule permissionRule = GrantPermissionRule.grant(android.Manifest.permission.CAMERA, android.Manifest.permission.ACCESS_FINE_LOCATION);

Solution 10 - Android

Thank you @niklas for the solution. In case anyone looking to grant multiple permissions in Java:

 @Rule
public GrantPermissionRule permissionRule = GrantPermissionRule.grant(android.Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.CAMERA);

Solution 11 - Android

I know an answer has been accepted, however, instead of the if statement that has been suggested over and over again, another more elegant approach would be to do the following in the actual test you want for a specific version of OS:

@Test
fun yourTestFunction() {
    Assume.assumeTrue(Build.VERSION.SDK_INT >= 23)
    // the remaining assertions...
}

If the assumeTrue function is called with an expression evaluating to false, the test will halt and be ignored, which I am assuming is what you want in case the test is being executed on a device pre SDK 23.

Solution 12 - Android

I've implemented a solution which leverages wrapper classes, overriding and build variant configuration. The solution is quite long to explain and is found over here: https://github.com/ahasbini/AndroidTestMockPermissionUtils.

It is not yet packed in an sdk but the main idea is to override the functionalities of ContextWrapper.checkSelfPermission and ActivityCompat.requestPermissions to be manipulated and return mocked results tricking the app into the different scenarios to be tested like: permission was denied hence the app requested it and ended with granted permission. This scenario will occur even if the app had the permission all along but the idea is that it was tricked by the mocked results from the overriding implementation.

Furthermore the implementation has a TestRule called PermissionRule class which can be used in the test classes to easily simulate all of the conditions to test the permissions seamlessly. Also assertions can be made like ensuring the app has called requestPermissions() for example.

Solution 13 - Android

For allowing the permission, when necessary, I think the easiest way is to use Barista's PermissionGranter.allowPermissionsIfNeeded(Manifest.permission.GET_ACCOUNTS) directly in the test which requires this permission.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionargenkiwiView Question on Stackoverflow
Solution 1 - AndroidNiklasView Answer on Stackoverflow
Solution 2 - AndroidRoc BoronatView Answer on Stackoverflow
Solution 3 - AndroidriwnodennykView Answer on Stackoverflow
Solution 4 - AndroidJose AlcérrecaView Answer on Stackoverflow
Solution 5 - AndroiddenysView Answer on Stackoverflow
Solution 6 - AndroidSachini SamarasingheView Answer on Stackoverflow
Solution 7 - AndroidmurtView Answer on Stackoverflow
Solution 8 - AndroidCampbellMGView Answer on Stackoverflow
Solution 9 - AndroidNSKView Answer on Stackoverflow
Solution 10 - AndroidlearnerView Answer on Stackoverflow
Solution 11 - AndroidEdison SpencerView Answer on Stackoverflow
Solution 12 - AndroidahasbiniView Answer on Stackoverflow
Solution 13 - AndroidlomzaView Answer on Stackoverflow