Reputation: 1210
I have an application that reads SMSs. The app works fine when debugging but when testing it using android instrumented test it throws the following error
java.lang.SecurityException: Permission Denial: reading com.android.providers.telephony.SmsProvider
This is my test case
@RunWith(AndroidJUnit4.class)
public class SmsFetcherTest {
@Test
public void fetchTenSms() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getContext();
// Fails anyway.
// assertTrue(ContextCompat.checkSelfPermission(appContext,
// "android.permission.READ_SMS") == PackageManager.PERMISSION_GRANTED);
List<Sms> tenSms = new SmsFetcher(appContext)
.limit(10)
.get();
assertEquals(10, tenSms.size());
}
}
I'm new to instrumented tests. Is this is proper way to do this?
Or am I missing something?
Upvotes: 41
Views: 20890
Reputation: 596
You can create a Rule like this.
class SomeNamePermissionsRule(
private val permissions: Array<String>
) : TestWatcher() {
override fun starting(description: Description?) {
super.starting(description)
/**
* https://developer.android.com/reference/androidx/test/rule/GrantPermissionRule
* For tests running on Android SDKs >= API 28, use android. app. UiAutomation.
* grantRuntimePermission(String, String) instead.
* */
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val uiAutomation = InstrumentationRegistry.getInstrumentation().uiAutomation
val packageName = ApplicationProvider.getApplicationContext<Context>().packageName
permissions.forEach { uiAutomation.grantRuntimePermission(packageName, it) }
} else GrantPermissionRule.grant(*permissions)
}
}
And use on your test in this way.
class SomeNameTest {
@get:Rule val permissionsRule = SomeNamePermissionsRule(
arrayOf(android.Manifest.permission.READ_SMS)
)
@Test
fun someNameTest() {}
}
Upvotes: 0
Reputation: 1180
For some permissions Rules are not working - like Manifest.permission.BLUETOOTH. Not sure why.
Workaround is to use Mockito and mock Context - so when context.checkSelfPermission() is called by system it simply returns Permission Granted
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
private Context mockedContext;
private final String[] permissionArrayOne = new String[]{
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
"android.permission.BLUETOOTH",
"android.permission.BLUETOOTH_ADMIN",
"android.permission.BLUETOOTH_SCAN"};
@Before
public void setup(){
mockedContext = Mockito.mock(Context.class);
Mockito.when(mockedContext.checkPermission(ArgumentMatchers.anyString(),
ArgumentMatchers.anyInt(),
ArgumentMatchers.anyInt())).thenReturn(PackageManager.PERMISSION_GRANTED);
}
@Test
public void myPermTest() {
for (String onePerm : permissionArrayOne) {
Log.d(TAG, "myPermTest: " + onePerm + " " + mockedContext.checkSelfPermission(onePerm));
assertEquals(ContextCompat.checkSelfPermission(mockedContext, onePerm), PackageManager.PERMISSION_GRANTED);
}
}
}
Upvotes: 2
Reputation: 1234
For reusability I create an interface:
interface WriteExternalPermission {
@get:Rule val writeExternalPermission: GrantPermissionRule
get() = GrantPermissionRule.grant(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
And you can just implement it inside your test class, where this permission is needed.
Upvotes: 1
Reputation: 28793
If you use inheritance for instrumentation classes you should write @get:Rule
in parent class.
For location:
import androidx.test.rule.GrantPermissionRule
@RunWith(AndroidJUnit4::class)
open class SomeTest {
@get:Rule
val permissionRule: GrantPermissionRule = GrantPermissionRule.grant(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION)
If you add ACCESS_BACKGROUND_LOCATION
on device where Android < Q or permission that doesn't exist in AndroidManifest you will get an exception.
Upvotes: 1
Reputation: 19146
Use GrantPermissionRule
. Here's how:
Add the following dependency to app/build.gradle
:
dependencies {
...
androidTestImplementation 'androidx.test:rules:1.4.0'
}
Now add the following to your InstrumentedTest
class:
import androidx.test.rule.GrantPermissionRule;
public class InstrumentedTest {
@Rule
public GrantPermissionRule mRuntimePermissionRule = GrantPermissionRule.grant(Manifest.permission.READ_SMS);
...
}
Upvotes: 48
Reputation: 24907
You can grant the permission as follows:
@RunWith(AndroidJUnit4.class)
public class MyInstrumentationTest {
@Rule
public GrantPermissionRule permissionRule = GrantPermissionRule.grant(Manifest.permission.READ_SMS);
...
}
Upvotes: 12