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

时间:2011-07-07 12:17:09

标签: android unit-testing final android-contentresolver

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

我想使用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 个答案:

答案 0 :(得分:18)

以下是使用getContentResolver()。query从内容提供程序返回模拟数据的示例测试。

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

以下是一般步骤:

  1. 使用MatrixCursor
  2. 创建适当的光标
  3. 扩展MockContentProvider以返回创建的光标
  4. 使用addProvider和setContentResolver将提供程序添加到MockContentResolver
  5. 将MockContentResolver添加到扩展的MockContext
  6. 将上下文传递给测试中的类
  7. 因为查询是最终方法,所以您不仅需要模拟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());   
     } 
    

答案 1 :(得分:4)

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

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

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

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

答案 2 :(得分:2)

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

答案 3 :(得分:0)