Query using MockContentResolver leads to NullPointerException
Asked Answered
B

1

5

We have a JUnit test class which extends ActivityInstrumentationTestCase2<CommentActivity>. The test (and the class we're testing) use CommentContentProvider, which extends ContentProvider, to access the SQLite database, and we're getting a NullPointerException [full stack trace below] when running a query on the provider.

We instantiate a MockContentResolver as shown:

MockContentResolver mResolver;

public void setUp() {
    super.setUp();
    CommentContentProvider ccp = new CommentContentProvider();
    mResolver = new MockContentResolver();
    mResolver.addProvider(CommentContentProvider.AUTHORITY, ccp);
}

Later on, in our tests, when calling the following code, we get a NullPointerException:

Cursor mCursor = mResolver.query(Uri.parse(mUri), null, null, null, null);

We get the same result even if we wait to instantiate MockContentResolver until we have a copy of the activity under test:

mActivity = getActivity();
MockContentResolver mResolver = new MockContentResolver(mActivity);

We have verified that mActivity is not null.

A colleague stepped through the Android source (not installed on our system) and found that the proximate cause of the error is that getContext() returns null on the first line of ContentProvider.enforceReadPermissionInner().

We took a look at this question which originally seemed similar, but I think it was a different problem entirely. This question is also a similar symptom of a problem, but they didn't instantiate their MockContentResolver. We are having problems instantiating ours.

Here's the stack trace we're getting:

java.lang.NullPointerException
at android.content.ContentProvider$Transport.enforceReadPermissionInner(ContentProvider.java:449)
at android.content.ContentProvider$Transport.enforceReadPermission(ContentProvider.java:394)
at android.content.ContentProvider$Transport.query(ContentProvider.java:194)
at android.content.ContentResolver.query(ContentResolver.java:461)
at android.content.ContentResolver.query(ContentResolver.java:404)
at packagename.test.FooActivityTest.getNumCommentsForRecipient(FooActivityTest.java:84)
at packagename.test.FooActivityTest.testCommentEntryInternal(FooActivityTest.java:91)
at packagename.test.FooActivityTest.testCommentEntry1(FooActivityTest.java:108)
at java.lang.reflect.Method.invokeNative(Native Method)
at android.test.InstrumentationTestCase.runMethod(InstrumentationTestCase.java:214)
at android.test.InstrumentationTestCase.access$000(InstrumentationTestCase.java:36)
at android.test.InstrumentationTestCase$2.run(InstrumentationTestCase.java:189)
at android.app.Instrumentation$SyncRunnable.run(Instrumentation.java:1719)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4998)
at java.lang.reflect.Method.invokeNative(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:777)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:593)
at dalvik.system.NativeStart.main(Native Method)

How can we resolve this problem?

Bryan answered 16/3, 2014 at 21:27 Comment(0)
C
8

I had a similar problem when testing a content provider that internally relied on another content provider to write away some metadata.

First of all, you may be better off using the ProviderTestCase2 class, which will do most of the work for setting up the provider under test for you. It might make your life considerably easier. (For me this wasn't enough because it'll only help you with one provider, I needed two.)

If this is not possible for you, here's what did the trick for me:

Your query fails because your provider never had a context attached to it. You have to do this yourself, manually - which the documentation forgets to mention. Do this:

public void setUp() {
    super.setUp();
    CommentContentProvider ccp = new CommentContentProvider();

    // Add this line to attach context:
    ccp.attachInfo(mActivity, null);

    mResolver = new MockContentResolver();
    mResolver.addProvider(CommentContentProvider.AUTHORITY, ccp);
}

I'm not 100% sure which context to attach to keep your test isolated from the rest of the world, ProviderTestCase2 sets up a whole chain of mock contexts. If you're having issues, look at RenamingDelegatingContext and IsolatedContext, those are the ones ContentProviderTestCase2 uses. (Have a look at its setUp() method).

Hope this helps you!

Corrigible answered 18/3, 2014 at 8:42 Comment(6)
Thank you, THANK YOU, THANK YOU! That makes total sense. Attaching mActivity cleared up the NullPointerException, although I still need to fiddle with it to make it use the same context as the class under tests.Coenocyte
Glad it helped you. I know it drove me crazy for quite a while until I figured it out. Please consider accepting the answer so I can get the points. :)Corrigible
I'm not the original poster; I'm her teacher (which gives you an idea how distressed I was at not being able to solve the problem I assigned). I'll advise her not to mark the question answered until we've got it fully solved, but I strongly suspect some well-deserved points are in your future. :-)Coenocyte
Thanks, this makes a lot of sense and seems to have fixed the problem!Bryan
We did end up needing use RenamingDelegatingContext. Possibly not a great fix, but we also needed to use a MockContentResolver in our FooActivity itself for testing purposes. We set it up so that the FooActivityTest passed an extra in the intent which was a DEBUG flag, and FooActivity checked for the debug flag and used getActivity() to get the context if it was false, and created a MockContentResolver and did the whole shebang with creating a RenamingDelegatingContext and such in FooActivity, so that the save button saved to our testing database instead of the production/live database.Bryan
MockContentResolver and RenamingDelegatingContext are there for exactly this sort of thing, so don't worry about it not being a great fix. ;)Corrigible

© 2022 - 2024 — McMap. All rights reserved.