How to check the action that ContentProvider uses without affecting the production database?

PROBLEM

I have two Android classes that I want to test:

I currently have two test classes:

  • CommentContentProviderTest , which extends ProviderTestCase2<CommentContentProvider> and uses a MockContentResolver . It works great.
  • CommentActivityTest , which continues to ActivityInstrumentationTestCase2<CommentActivity> . This works fine except for the CommentActivity parts that access the CommentContentProvider .

The problem is that when CommentActivity refers to CommentContentProvider , it does this through the standard ContentResolver :

 ContentResolver resolver = getContentResolver(); Cursor cursor = resolver().query(...); 

Thus, when CommentActivityTest starts, it starts CommentActivity , which accesses (reads and writes) the production database, as shown in the two lines above.

My question is how to do CommentActivity using the standard ContentResolver in production, but MockContentResolver during the test.

RELATED QUESTIONS

  • This is different from testing an Android device with ContentProviders and other questions I found about testing ContentProviders, because they can extend the android.test classes designed to test ContentProviders, while I need to extend the class for testing Activity .
  • This is similar to How to add dependency when testing Android activity without a third-party structure? , which was also published by me, but did not respond. Now I am ready to use a third-party structure if this helps.
  • A query using MockContentResolver leads to a NullPointerException connected and leads to a solution in Option 1 below, but I don't know if this is the best solution in my case.

POSSIBLE SOLUTIONS

It would be nice if I could insert a ContentResolver (maybe a MockContentResolver or RenamingDelegatingContext ) through an Intent that starts CommentActivity , but I can't do it, since Context not Parcelable .

Which of the following options is best, or is there a better option?

OPTION 1

Add a debug flag to Intent , which will launch CommentActivity :

 public class CommentActivity extends Activity { public static final String DEBUG_MODE = "DEBUG MODE"; private ContentResolver mResolver; @Override protected void onCreate(Bundle savedInstanceState) { : // If the flag is not present, debugMode will be set to false. boolean debugMode = getIntent().getBooleanExtra(DEBUG_MODE, false); if (debugMode) { // Set up MockContentResolver or DelegatingContextResolver... } else { mResolver = getContentResolver(); } : } 

I don’t like this option because I don’t like putting test code in my non-test classes.

OPTION 2

Use the abstract factory template to pass in a Parcelable class that either provides real ContentProvider or MockContentProvider :

 public class CommentActivity extends Activity { public static final String FACTORY = "CONTENT RESOLVER FACTORY"; private ContentResolver mResolver; @Override protected void onCreate(Bundle savedInstanceState) { : ContentResolverFactory factory = getIntent().getParcelableExtra(FACTORY); mResolver = factory.getContentResolver(this); : } 

where I also have:

 public abstract class ContentResolverFactory implements Parcelable { public abstract ContentResolver getContentResolver(Context context); } public abstract class RealContentResolverFactory extends ContentResolverFactory public ContentResolver getContentResolver(Context context) { return context.getContextResolver(); } } public abstract class MockContentResolverFactory extends ContentResolverFactory public ContentResolver getContentResolver(Context context) { MockContentResolver resolver = new MockContentResolver(); // Set up MockContentResolver... return resolver; } } 

During production, I pass (through intent) an instance of RealContentResolverFactory , and in the test I pass an instance of MockContentResolverFactory . Since none of them has any state, they can easily be shipped / Serializable.

My concern with this approach is that I do not want to be β€œthis guy” who is abusing design patterns when simpler approaches exist.

OPTION 3

Add the following method to CommentActivity :

 public void static setContentResolver(ContentResolver) { : } 

This is cleaner than option 1, since it creates a ContentResolver outside of CommentActivity , but like option 1, it requires modification of the class being tested.

OPTION 4

For << 294> ActivityUnitTestCase<CommentActivity> instead of ActivityInstrumentationTestCase2<CommentActivity> . This allows me to set the CommentActivity context through setActivityContext() . Context I pass overrides the usual getContentResolver() to use MockContentResolver (which I initialize elsewhere).

 private class MyContext extends RenamingDelegatingContext { MyContext(Context context) { super(context, FILE_PREFIX); } @Override public ContentResolver getContentResolver() { return mResolver; } } 

This works and does not require modification of the tested class, but adds more complexity, since ActivityUnitTestCase<CommentActivity>.startActivity() cannot be called in the setUp( ) method for the API .

Another inconvenience is that the activity has to be checked in touch mode, and setActivityInitialTouchMode (boolean) is defined in ActivityInstrumentationTestCase2<T> , but not ActivityUnitTestCase<T> .

FWIW, I am a little obsessive about this because I will represent it in the Android development class that I teach.

+7
android dependency-injection android-contentresolver abstract-factory android-testing
source share
1 answer

Option 2 seems better to me. I am not worried about using factory; I am more concerned about the intent, causing a change in behavior at a distance. But other solutions put non-performing code in production code, so what you are testing is not very similar to how everything works in production. Hope this helps.

+1
source share

All Articles