10

我正在尝试测试查询内容解析器的类。

我想使用MockContentResolver和模拟query方法。

问题是这种方法是最终的。我该怎么办?使用模拟框架?嘲笑其他班级?提前致谢。

public class CustomClass {

    private ContentResolver mContentResolver;

    public CustomClass(ContentResolver contentResolver) {
        mContentResolver = contentResolver;
    }

    public String getConfig(String key) throws NoSuchFieldException {
        String value = null;

            Cursor cursor = getContentResolver().query(...);
            if (cursor.moveToFirst()) {
                //...
            }
        //..
    }
}
4

5 回答 5

18

下面是一个示例测试,它使用 getContentResolver().query 从内容提供者返回模拟数据。

它应该适用于任何内容提供者,只需进行一些修改,但此示例模拟从联系人内容提供者返回的电话号码

以下是一般步骤:

  1. 使用 MatrixCursor 创建适当的光标
  2. 扩展 MockContentProvider 以返回创建的游标
  3. 使用 addProvider 和 setContentResolver 将提供程序添加到 MockContentResolver
  4. 将 MockContentResolver 添加到扩展的 MockContext
  5. 将上下文传递给被测类

因为 query 是 final 方法,所以你不仅需要 mock MockContentProvider,还需要 mock MockContentResolver。否则在查询方法中调用acquireProvider时会报错。

这是示例代码:

public class MockContentProviderTest extends AndroidTestCase{
    public void testMockPhoneNumbersFromContacts(){
        //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 Provider
        String[] exampleData = {"(979) 267-8509"}; 
        String[] examleProjection = new String[] { ContactsContract.CommonDataKinds.Phone.NUMBER};
        MatrixCursor matrixCursor = new MatrixCursor(examleProjection);
        matrixCursor.addRow(exampleData);

        //Step 2: Create a stub content provider and add the matrix cursor as the expected result of the query
        HashMapMockContentProvider mockProvider = new HashMapMockContentProvider();
        mockProvider.addQueryResult(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, matrixCursor);

        //Step 3: Create a mock resolver and add the content provider.
        MockContentResolver mockResolver = new MockContentResolver();
        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 context
        ContextWithMockContentResolver mockContext = new ContextWithMockContentResolver(super.getContext());
        mockContext.setContentResolver(mockResolver);

        //Example Test 
        ExampleClassUnderTest underTest = new ExampleClassUnderTest();
        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 query
     public class HashMapMockContentProvider extends MockContentProvider{
         private HashMap<Uri, Cursor> expectedResults = new HashMap<Uri, Cursor>();
         public void addQueryResult(Uri uriIn, Cursor expectedResult){
             expectedResults.put(uriIn, expectedResult);
         }
         @Override
         public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder){
                return expectedResults.get(uri);
         } 
     }

     public class ContextWithMockContentResolver extends RenamingDelegatingContext {
            private ContentResolver contentResolver;
            public void setContentResolver(ContentResolver contentResolver){ this.contentResolver = contentResolver;}
            public ContextWithMockContentResolver(Context targetContext) { super(targetContext, "test");}
            @Override public ContentResolver getContentResolver() { return contentResolver; }
            @Override public Context getApplicationContext(){ return this; } //Added in-case my class called getApplicationContext() 
     }

     //An example class under test which queries the populated cursor to get the expected phone number 
     public class ExampleClassUnderTest{
         public  String getPhoneNumbers(Context context){//Query for  phone numbers from contacts
                String[] projection = new String[]{ 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));
         }
     }
}

如果您不想传递上下文:

如果你想让它在被测类中由 getContext() 返回而不是传递它,你应该能够像这样在你的 android 测试中覆盖 getContext()

@Override
 public Context getContext(){
    return new ContextWithMockContentResolver(super.getContext());   
 } 
于 2014-01-24T23:44:45.693 回答
5

这个问题已经很老了,但人们可能仍然像我一样面临这个问题,因为没有太多关于测试的文档。

对我来说,对于依赖于内容提供者(来自 android API)的测试类,我使用了 ProviderTestCase2

public class ContactsUtilityTest extends ProviderTestCase2<OneQueryMockContentProvider> {


private ContactsUtility contactsUtility;

public ContactsUtilityTest() {
    super(OneQueryMockContentProvider.class, ContactsContract.AUTHORITY);
}


@Override
protected void setUp() throws Exception {
    super.setUp();
    this.contactsUtility = new ContactsUtility(this.getMockContext());
}

public void testsmt() {
    String phoneNumber = "777777777";

    String[] exampleData = {phoneNumber};
    String[] examleProjection = new String[]{ContactsContract.PhoneLookup.NUMBER};
    MatrixCursor matrixCursor = new MatrixCursor(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_URI
    assertTrue(result);
}


}


public class OneQueryMockContentProvider extends MockContentProvider {
private Cursor queryResult;

public void addQueryResult(Cursor expectedResult) {
    this.queryResult = expectedResult;
}

@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
    return this.queryResult;
}
}

它是使用 Jenn Weingarten 的答案编写的。需要注意的几件事: -您MockContentProvider必须是公开的-您必须使用Contextfrom 方法this.getMockContext()而不是this.getContext()在您的测试类中,否则您将访问的不是模拟数据,而是来自设备的真实数据(在这种情况下 - 联系人)-测试不能运行AndroidJUnit4 runner -Test 当然必须作为 android Instrumented test 运行 -测试构造函数中的第二个参数(授权)必须与在被测类中查询的 URI 相同 -模拟提供程序的类型必须作为类参数提供

基本上 ProviderTestCase2 可以让你初始化模拟上下文、模拟内容解析器和模拟内容提供者。

我发现使用旧的测试方法更容易,而不是尝试使用 mockito 和 junit4 为高度依赖于 android api 的类编写本地单元测试。

于 2016-02-28T09:06:22.510 回答
2

阅读文档后,我能够编写MockContentProvider相应游标的实现返回。然后我将此提供程序添加到MockContentResolverusing addProvider.

于 2011-07-10T17:29:19.813 回答
1

这是一个关于如何使用mockk库和 Kotlin 存根 ContentResolver 的示例。

注意:如果您在模拟器中运行此测试,此测试似乎不起作用,在具有 API 23 的模拟器中失败并出现此错误"java.lang.ClassCastException: android.database.MatrixCursor cannot be cast to java.lang.Boolean"

澄清这一点,让我们这样做。有一个来自 Context 对象的扩展,称为 ,val Context.googleCalendars: List<Pair<Long, String>>这个扩展过滤日历,日历名称不以“@google.com”结尾,我正在使用这个 AndroidTest 测试这个扩展的正确行为。

是的,你可以从这里下载 repo 。

@Test
    fun getGoogleCalendarsTest() {

        // mocking the context
        val mockedContext: Context = mockk(relaxed = true)

        // mocking the content resolver
        val 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 ContentResolver
        val 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))
    }
于 2021-09-28T21:58:45.827 回答
0

我还没有使用过 Mockito,但是对于内容提供商,你可以依赖 Robolectric。https://github.com/juanmendez/jm_android_dev/blob/master/16.observers/00.magazineAppWithRx/app/src/test/java/ContentProviderTest.java

于 2016-04-09T05:16:50.117 回答