The new permissions scheme introduced by Android Marshmallow requires checking for specific permissions at runtime, which implies the need to provide different flows dependi
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.
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);
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.
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)
}
}
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.
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.