如何使用内容解析器/提供者测试类?

发布于 2024-11-19 06:58:03 字数 611 浏览 2 评论 0 原文

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

我想使用 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()) {
                //...
            }
        //..
    }
}

I'm trying to test class that queries content resolver.

I would like to use MockContentResolver and mock query method.

The problem is that this method is final. What should I do? Use mocking framework? Mock other class? Thanks in advance.

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()) {
                //...
            }
        //..
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

撕心裂肺的伤痛 2024-11-26 06:58:03

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

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

以下是一般步骤:

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

因为 query 是一个最终方法,所以您不仅需要模拟MockContentProvider 也是 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());   
 } 

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:

  1. Creates appropriate cursor using MatrixCursor
  2. Extend MockContentProvider to return the created cursor
  3. Add the provider to a MockContentResolver using the addProvider and setContentResolver
  4. Add the MockContentResolver to an extended MockContext
  5. 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:

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));
         }
     }
}

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

@Override
 public Context getContext(){
    return new ContextWithMockContentResolver(super.getContext());   
 } 
花开柳相依 2024-11-26 06:58:03

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

对我来说,为了测试依赖于内容提供者(来自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必须是公开的
-您必须在测试类中使用 this.getMockContext() 方法中的 Context 而不是 this.getContext(),否则您将访问不是模拟数据,而是来自设备的真实数据(在本例中为联系人)
- 测试不得使用 AndroidJUnit4 运行程序运行
- 测试当然必须作为 Android 仪器测试运行
- 测试构造函数中的第二个参数(权威)必须与被测类中查询的 URI 相同
- 模拟提供程序的类型必须作为类参数提供

基本上 ProviderTestCase2 可以帮助您初始化模拟上下文、模拟内容解析器和模拟内容提供程序。

我发现使用旧的测试方法比尝试使用mockito和junit4为高度依赖于android api的类编写本地单元测试要容易得多。

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

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;
}
}

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.

浅忆 2024-11-26 06:58:03

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

注意:如果您在模拟器中运行此测试,此测试似乎不起作用,在具有 API 23 的模拟器中失败,并出现此错误 “java.lang.ClassCastException:android.database.MatrixCursor 无法转换为 java.lang。布尔值”

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

是的,您可以从此处下载存储库。

@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, "[email protected]"))
            this.addRow(arrayOf(2, "name02")) // this row must be filtered by the extension.
            this.addRow(arrayOf(3, "[email protected]"))
        }

        // 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))
    }

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.

@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, "[email protected]"))
            this.addRow(arrayOf(2, "name02")) // this row must be filtered by the extension.
            this.addRow(arrayOf(3, "[email protected]"))
        }

        // 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))
    }
潜移默化 2024-11-26 06:58:03

阅读文档后,我能够编写 MockContentProvider 来实现适当游标的返回。然后我使用 addProvider 将此提供程序添加到 MockContentResolver 中。

After reading docs I was able to write MockContentProvider that implemented return of appropriate cursors. Then I added this provider to MockContentResolver using addProvider.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文