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?
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!
Related
Here's a little background. The process of querying the database (QueryDB) in my app begins in MainActivity.onCreate, where I have this code:
assets = getAssets(); // the SQLite database
DatabaseConnector
dbc = new DatabaseConnector(getApplicationContext(), assets);
dbc.setDbProcesslistener(this); // set way to know matches has been defined
dbc.findDBMatches();
And in the file in my question (named DatabaseConnector) I have:
void findDBMatches()
{
mContext.startService(new Intent(mContext, QueryDB.class));
}
Here's where the problem manifests itself. This code segment ...
public static class QueryDB extends IntentService
{
public QueryDB(String name)
{
super(name);
}
results in this error:
? E/libprocessgroup: failed to make and chown /acct/uid_10058: Read-only file system
? W/Zygote: createProcessGroup failed, kernel missing CONFIG_CGROUP_CPUACCT?
com.dslomer64.servyhelperton E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.dslomer64.servyhelperton, PID: 335
java.lang.RuntimeException: Unable to instantiate service
com.dslomer64.servyhelperton.DatabaseConnector$QueryDB:
java.lang.InstantiationException: class
com.dslomer64.servyhelperton.DatabaseConnector$QueryDB has no zero argument constructor
The error doesn't tell me a line, but it does mention QueryDB. So I insert a zero-argument constructor for QueryDB and get an immediate error:
I circled extends IntentService because, in findDBMatches, I start a service for QueryDB and the first error message says Unable to instantiate service. However, in debugging, I found that execution didn't fail at the line mContext.startService(new Intent(mContext, QueryDB.class));. I had breakpoints set in the constructors for QueryDB but execution didn't go there.
I'm lost.
Before (foolishly) taking AS's advice about changes, the app worked fine. Now I could go back through the history and revert to the version before I began the changes, but there were plenty (of warnings) that I got rid of and I'd rather not do that. If anyone can, with such short snippets of code, suggest a fix, I can try it and maybe be good to go.
Further notes:
App won't work without QueryDB extending IntentService (get other immediate errors).
Note that mContext WAS declared static (which I now know not to do because of memory leaks) but it doesn't matter whether it's static or not. Same errors.
use super("QueryDB"); inside the constructor
public QueryDB {
super("QueryDB");
}
I have to build an app with sqlite usage. Now I want to write my unit tests. These unit tests should test my class SQLiteBridge. SQLiteBridge provides DAOs for every child class of Model.
Now I got the problem that I need a context to create my SQLiteBridge. SQLiteBridge creates and handles a SQLite database on the system..
Where to get the Context-Object from?
My setup is like here (so I'm using Junit4 [thanks god]):
http://tools.android.com/tech-docs/unit-testing-support
EDIT: I hope there is a way like the old AndroidTestCase to extend without losing Junit4. :)
As described here: https://code.google.com/p/android-test-kit/wiki/AndroidJUnitRunnerUserGuide
Use the InstrumentationRegistry to obtain the context.
However if you call InstrumentationRegistry.getContext() directly you may get an exception opening your database. I believe this is because the context returned by getContext() points to the instrumentation's context rather than that of your application / unit test. Instead use InstrumentationRegistry.getInstrumentation().getTargetContext()
For example:
#RunWith(AndroidJUnit4.class)
public class SqliteTest {
Context mMockContext;
#Before
public void setUp() {
mMockContext = new RenamingDelegatingContext(InstrumentationRegistry.getTargetContext(), "test_");
}
}
The RenamingDelegatingContext simply prefixes the file/database names with test_ to prevent you from overwriting data that you may have in the same simulator.
jUnit 4 (and perhaps other versions of jUnit) and androidx use:
ApplicationProvider.getApplicationContext();
See: Android Documentation
I have an app that uses ContentProvider to access SQLite. An instance of SQLiteOpenHelper is created in providers onCreate:
#Override
public boolean onCreate() {
final Context context = getContext();
mDBHelper = new MyDatabase(context);
return true;
}
SQLiteDatabase instances retrieved in methods insert/update/delete/query are not manually closed. None of these methods are marked synchronized. ContentProvider is accessed from multiple threads started from UI and services.
Sample stacktrace:
android.database.sqlite.SQLiteDiskIOException: disk I/O error (code 3850)
at android.database.sqlite.SQLiteConnection.nativeExecuteForChangedRowCount(Native Method)
at android.database.sqlite.SQLiteConnection.executeForChangedRowCount(SQLiteConnection.java:734)
at android.database.sqlite.SQLiteSession.executeForChangedRowCount(SQLiteSession.java:754)
at android.database.sqlite.SQLiteStatement.executeUpdateDelete(SQLiteStatement.java:64)
at android.database.sqlite.SQLiteDatabase.updateWithOnConflict(SQLiteDatabase.java:1574)
at android.database.sqlite.SQLiteDatabase.update(SQLiteDatabase.java:1520)
at com.sample.provider.MyProvider.update(SourceFile:0)
at android.content.ContentProvider$Transport.update(ContentProvider.java:260)
at android.content.ContentResolver.update(ContentResolver.java:1040)
Things worked fine up to the moment when I added a class that serializes writes to certain tables using a Handler initialized by Looper from HandlerThread. After this I started seeing plenty of SQLiteDiskIOExceptions with error codes 3850 and 0 (not an error?). Interestingly 90% of these crashes occur with Nexus 4 and on a handful of other devices.
I have been running Unit tests trying to simulate the condition but have been unable to reproduce the problem. There are other questions that already discuss related issues (e.g. here: Synchronize access to Content Provider) but to me the original cause for this error seems still a bit unclear. So what really are the reasons for error 3850?
I followed the advice given here in several posts on how to declare global contants:
public class Constants {
public static final int i1 = 1;
public static final int i2 = 2;
}
I just include this class in my project and refer to the constants like this:
in any other class...
GlobalsVars.gi1 = Constants.i1;
(ps I hope this is OK and do not need to do anything to the Constants class like initializing or anything.)
But as I found out here: assigning int to Integer using static global variables is not a good idea. My app crashes sometimes when accessing the constants.
Though I find it really weird, since my app is rather small, but may be the Constants class - not an activity - is really removed from the memory in certain cases, though I access its constants in all my activities. That's why I would think it should not be removed from memory anyway.
But for sure, my app crashes in certain cases when accessing the Constants.i1 value.
What would be the best way just to declare some constants in a reliable way. (In c-Derivatives there are the easy to use macros) But there is nothing like this in Android.
-> all I need are "reliable" constants in Java...
EDIT:
declaration of GlobalVars class added
public class GlobalVars {
public static Integer gi1;
public static Integer gi2;
}
Many thanks
EDIT:
added crash log
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.xxxx.xxxx/com.xxxx.xxxx.screens.One_screen}: java.lang.NullPointerException
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1830)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1851)
at android.app.ActivityThread.access$1500(ActivityThread.java:132)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1038)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:150)
at android.app.ActivityThread.main(ActivityThread.java:4293)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:849)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:607)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NullPointerException
at com.xxxx.xxxx.screens.Settings_screen.presentOnScreen(One_screen.java:172)
at com.xxxx.xxxx.screens.Settings_screen.onCreate(One_screen.java:49)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1072)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1794)
... 11 more
and the line 172 in One_screen is:
if (GlobalVars.gi1 == Constants.i1){
The more general problem you are encountering is how to save state across several Activities and all parts of your application. A static variable (for instance, a singleton) is a common Java way of achieving this. I have found however, that a more elegant way in Android is to associate your state with the Application context.
As you know, each Activity is also a Context, which is information about its execution environment in the broadest sense. Your application also has a context, and Android guarantees that it will exist as a single instance across your application.
The way to do this is to create your own subclass of android.app.Application, and then specify that class in the application tag in your manifest. Now Android will automatically create an instance of that class and make it available for your entire application. You can access it from any context using the Context.getApplicationContext() method (Activity also provides a method getApplication() which has the exact same effect):
class MyApp extends Application {
private String myState;
public String getState(){
return myState;
}
public void setState(String s){
myState = s;
}
}
class Blah extends Activity {
#Override
public void onCreate(Bundle b){
...
MyApp appState = ((MyApp)getApplicationContext());
String state = appState.getState();
...
}
}
This has essentially the same effect as using a static variable or singleton, but integrates quite well into the existing Android framework. Note that this will not work across processes (should your app be one of the rare ones that has multiple processes).
Your NullPointerException is occurring because GlobalVars.gi1 is null, not Constants.i1. You should always be able to rely on hard-coded integer values as they are part of the class definition.
If you simply wish to store a small number of long-lived integer variables. I suggest looking into SharedPreferences to store them (instead of GlobalVars).
You can find out more here.
If you wish to pass data only from one Activity to another, look at adding values to the Intent's extras using Intent.putExtra and retrieve the extras Bundle in the next Activity using Intent.getExtras on the Intent retrieved from Activity.getIntent.
You don't need static in Java to make them constant. You can make them final and public and access them relatively the same way. The difference though, is you'll have to create an instance of the class with a reference ever single time you want to access the constants which consumes memory for no real reason. You can do the address this issue by following the Singleton pattern which will create a single instance of the class which you can access through the static getInstance() method.
Please post the logs of the crash. After reading your other post (the one you linked to) I'm certain the problem lies elsewhere. For instance, in your other question you mention in a comment to an answer:
I got a crash report of my app in a place where it compares if (GlobalVars.gi1 == Constants.i1)
Autoboxing in Java5+ supports this type of comparison. The code given works universally.
I'm writing some tests to test my sqllite database code. Can someone here explain if there would be a difference writing those tests using the context I get from AndroidTestCase.getContext() or using an IsolatedContext.
For those that don't want to follow the link to the Google Group, here is the answer given there:
AndroidTestCase.getContext() returns a normal Context object. It's the
Context of the test case, not the component under test.
IsolatedContext returns a "mock" Context. I put "mock" in quotes
because its not a mock in the normal sense of that term (for testing).
Instead, it's a template Context that you have to set up yourself. It
"isolates" you from the running Android system, so that your Context
or your test doesn't accidentally get outside of the test fixture. For
example, an IsolatedContext won't accidentally hit a production
database (unless you set it up to do that!) Note, however, that some
of the methods in an IsolatedContext may throw exceptions.
IsolatedContext is documented in the Developer Guide under Framework
Topics > Testing, both in Testing Fundamentals and in Content Provider
Testing.
Here is the Android docs on IsolatedContext.
And here is the relevant section of the Testing Fundamentals document.
The answer:
http://groups.google.com/group/android-developers/browse_thread/thread/3a7bbc78258a194a?tvc=2
I had the simple problem: I need to test my DAO class without touching the real database. So I found the IsolatedContext from docs. But finally I found the other context in the same docs: RenamingDelegatingContext might be more easier to use. Here is my test case:
public class AddictionDAOTest extends AndroidTestCase {
#Override
public void setUp() throws Exception {
super.setUp();
setContext(new RenamingDelegatingContext(getContext(), "test_"));
}
public void testReadAllAddictions() throws Exception {
ImQuitDAO imQuitDAO = new ImQuitDAO(getContext());
...
}
}