How To Test Class Using Content Resolver/provider?
Solution 1:
Here is an example test that returns mock data from a content provider using getContentResolver().query.
It should work for any content provider, with a few modifications, but this example mocks returning phone numbers from the Contacts content provider
Here are the general steps:
- Creates appropriate cursor using MatrixCursor
- Extend MockContentProvider to return the created cursor
- Add the provider to a MockContentResolver using the addProvider and setContentResolver
- Add the MockContentResolver to an extended MockContext
- Passes the context into the the class under test
Because query is a final method, you need to mock not only MockContentProvider but also MockContentResolver. Otherwise you will get an error when acquireProvider is called during the query method.
Here is the example code:
publicclassMockContentProviderTestextendsAndroidTestCase{
publicvoidtestMockPhoneNumbersFromContacts(){
//Step 1: Create data you want to return and put it into a matrix cursor//In this case I am mocking getting phone numbers from Contacts ProviderString[] exampleData = {"(979) 267-8509"};
String[] examleProjection = newString[] { ContactsContract.CommonDataKinds.Phone.NUMBER};
MatrixCursor matrixCursor = newMatrixCursor(examleProjection);
matrixCursor.addRow(exampleData);
//Step 2: Create a stub content provider and add the matrix cursor as the expected result of the queryHashMapMockContentProvider mockProvider = newHashMapMockContentProvider();
mockProvider.addQueryResult(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, matrixCursor);
//Step 3: Create a mock resolver and add the content provider.MockContentResolver mockResolver = newMockContentResolver();
mockResolver.addProvider(ContactsContract.AUTHORITY/*Needs to be the same as the authority of the provider you are mocking */, mockProvider);
//Step 4: Add the mock resolver to the mock contextContextWithMockContentResolver mockContext = newContextWithMockContentResolver(super.getContext());
mockContext.setContentResolver(mockResolver);
//Example Test ExampleClassUnderTest underTest = newExampleClassUnderTest();
String result = underTest.getPhoneNumbers(mockContext);
assertEquals("(979) 267-8509",result);
}
//Specialized Mock Content provider for step 2. Uses a hashmap to return data dependent on the uri in the querypublicclassHashMapMockContentProviderextendsMockContentProvider{
privateHashMap<Uri, Cursor> expectedResults = newHashMap<Uri, Cursor>();
publicvoidaddQueryResult(Uri uriIn, Cursor expectedResult){
expectedResults.put(uriIn, expectedResult);
}
@OverridepublicCursorquery(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder){
return expectedResults.get(uri);
}
}
publicclassContextWithMockContentResolverextendsRenamingDelegatingContext {
privateContentResolver contentResolver;
publicvoidsetContentResolver(ContentResolver contentResolver){ this.contentResolver = contentResolver;}
publicContextWithMockContentResolver(Context targetContext) { super(targetContext, "test");}
@OverridepublicContentResolvergetContentResolver() { return contentResolver; }
@OverridepublicContextgetApplicationContext(){ returnthis; } //Added in-case my class called getApplicationContext()
}
//An example class under test which queries the populated cursor to get the expected phone number publicclassExampleClassUnderTest{
publicStringgetPhoneNumbers(Context context){//Query for phone numbers from contactsString[] projection = newString[]{ ContactsContract.CommonDataKinds.Phone.NUMBER};
Cursor cursor= context.getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, projection, null, null, null);
cursor.moveToNext();
return cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
}
}
}
If you don't want to pass context in:
If you wanted to have it returned by getContext() in the class under test instead of passing it in you should be able to override getContext() in your android test like this
@OverridepublicContextgetContext(){
returnnewContextWithMockContentResolver(super.getContext());
}
Solution 2:
This question is pretty old but people might still face the issue like me, because there is not a lot of documentation on testing this.
For me, for testing class which was dependent on content provider (from android API) I used ProviderTestCase2
publicclassContactsUtilityTestextendsProviderTestCase2<OneQueryMockContentProvider> {
privateContactsUtility contactsUtility;
publicContactsUtilityTest() {
super(OneQueryMockContentProvider.class, ContactsContract.AUTHORITY);
}
@OverrideprotectedvoidsetUp() throws Exception {
super.setUp();
this.contactsUtility = newContactsUtility(this.getMockContext());
}
publicvoidtestsmt() {
String phoneNumber = "777777777";
String[] exampleData = {phoneNumber};
String[] examleProjection = newString[]{ContactsContract.PhoneLookup.NUMBER};
MatrixCursor matrixCursor = newMatrixCursor(examleProjection);
matrixCursor.addRow(exampleData);
this.getProvider().addQueryResult(matrixCursor);
boolean result = this.contactsUtility.contactBookContainsContact(phoneNumber);
// internally class under test use this.context.getContentResolver().query(); URI is ContactsContract.PhoneLookup.CONTENT_FILTER_URIassertTrue(result);
}
}
publicclassOneQueryMockContentProviderextendsMockContentProvider {
privateCursor queryResult;
publicvoidaddQueryResult(Cursor expectedResult) {
this.queryResult = expectedResult;
}
@OverridepublicCursorquery(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
returnthis.queryResult;
}
}
It's written by using Jenn Weingarten's answer.
Few things to note:
-your MockContentProvider
must be public
-you must use Context
from method this.getMockContext()
instead of this.getContext()
in your class under test, otherwise you will access not mock data but real data from device (in this case - contacts)
-Test must not be run with AndroidJUnit4 runner
-Test of course must be run as android instrumented test
-Second parameter in constructor of the test (authority) must be same compared to URI queried in class under test
-Type of mock provider must be provided as class parameter
Basically ProviderTestCase2 makes for you initializing mock context, mock content resolver and mock content provider.
I found it much more easier to use older method of testing instead of trying to write local unit test with mockito and junit4 for class which is highly dependent on android api.
Solution 3:
After reading docs I was able to write MockContentProvider
that implemented return of appropriate cursors. Then I added this provider to MockContentResolver
using addProvider
.
Solution 4:
Here is an example about how to stub a ContentResolver with mockk Library and Kotlin.
NOTE: this test seems that is not working if you run this in an emulator, fails in an emulator with API 23 with this error "java.lang.ClassCastException: android.database.MatrixCursor cannot be cast to java.lang.Boolean"
.
Clarified that, lets do this. Having an extension from Context object, that is called, val Context.googleCalendars: List<Pair<Long, String>>
, this extension filters calendars witch calendar name doesn't ends with "@google.com", I am testing the correct behavior of this extension with this AndroidTest.
Yes you can download the repo from here.
@TestfungetGoogleCalendarsTest() {
// mocking the contextval mockedContext: Context = mockk(relaxed = true)
// mocking the content resolverval mockedContentResolver: ContentResolver = mockk(relaxed = true)
val columns: Array<String> = arrayOf(
CalendarContract.Calendars._ID,
CalendarContract.Calendars.NAME
)
// response to be stubbed, this will help to stub// a response from a query in the mocked ContentResolverval matrixCursor: Cursor = MatrixCursor(columns).apply {
this.addRow(arrayOf(1, "username01@gmail.com"))
this.addRow(arrayOf(2, "name02")) // this row must be filtered by the extension.this.addRow(arrayOf(3, "username02@gmail.com"))
}
// stubbing content resolver in the mocked context.
every { mockedContext.contentResolver } returns mockedContentResolver
// stubbing the query.
every { mockedContentResolver.query(CalendarContract.Calendars.CONTENT_URI, any(), any(), any(), any()) } returns matrixCursor
val result: List<Pair<Long, String>> = mockedContext.googleCalendars
// since googleCalendars extension returns only the calendar name that ends with @gmail.com// one row is filtered from the mocked response of the content resolver.
assertThat(result.isNotEmpty(), Matchers.`is`(true))
assertThat(result.size, Matchers.`is`(2))
}
Solution 5:
I haven't used Mockito yet, but for content providers, you can rely on Robolectric. https://github.com/juanmendez/jm_android_dev/blob/master/16.observers/00.magazineAppWithRx/app/src/test/java/ContentProviderTest.java
Post a Comment for "How To Test Class Using Content Resolver/provider?"