Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Robolectric accessing database throws an error

I have a test that creates an activity which attempts to get some data from the database. This fails with SQLiteException

17:40:40.528 [DEBUG] [TestEventLogger]     android.database.sqlite.SQLiteException: Cannot open SQLite connection, base error code: 14
17:40:40.528 [DEBUG] [TestEventLogger]      at org.robolectric.shadows.ShadowSQLiteConnection.rethrow(ShadowSQLiteConnection.java:53)
17:40:40.528 [DEBUG] [TestEventLogger]      at org.robolectric.shadows.ShadowSQLiteConnection.access$600(ShadowSQLiteConnection.java:30)
17:40:40.529 [DEBUG] [TestEventLogger]      at org.robolectric.shadows.ShadowSQLiteConnection$Connections.execute(ShadowSQLiteConnection.java:443)
17:40:40.529 [DEBUG] [TestEventLogger]      at org.robolectric.shadows.ShadowSQLiteConnection$Connections.open(ShadowSQLiteConnection.java:345)
17:40:40.529 [DEBUG] [TestEventLogger]      at org.robolectric.shadows.ShadowSQLiteConnection.nativeOpen(ShadowSQLiteConnection.java:58)
17:40:40.529 [DEBUG] [TestEventLogger]      at android.database.sqlite.SQLiteConnection.nativeOpen(SQLiteConnection.java)
17:40:40.529 [DEBUG] [TestEventLogger]      at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:209)
17:40:40.529 [DEBUG] [TestEventLogger]      at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:193)
17:40:40.529 [DEBUG] [TestEventLogger]      at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:463)
17:40:40.530 [DEBUG] [TestEventLogger]      at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:185)
17:40:40.530 [DEBUG] [TestEventLogger]      at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:177)
17:40:40.530 [DEBUG] [TestEventLogger]      at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:806)
17:40:40.530 [DEBUG] [TestEventLogger]      at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:791)
17:40:40.530 [DEBUG] [TestEventLogger]      at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:694)
17:40:40.530 [DEBUG] [TestEventLogger]      at android.app.ContextImpl.openOrCreateDatabase(ContextImpl.java:1142)
17:40:40.530 [DEBUG] [TestEventLogger]      at android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.java:267)
17:40:40.531 [DEBUG] [TestEventLogger]      at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:223)
17:40:40.531 [DEBUG] [TestEventLogger]      at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:163)

This used to work fine before I moved my database class to a singleton model. Any suggestions how this should be handled with Robolectric? I couldn't find any documentation or samples on this.

EDIT:

Running Robolectric 3.0 RC-2

Robolectric is driving my activity which attempts to do some work with the database. In my app DB class, removing the check for instance == null from below 'fixes' the issue (i.e. there's no issue with Robolectric running the test if MySQLiteOpenHelper is recreated every time)

public static synchronized MyDataManager getInstance(Context context){
    if (sInstance == null) {
        sInstance = new MyDataManager(context.getApplicationContext());
    }
    return sInstance;
}

private MyDataManager(Context context) {
    dbHelper = new MySQLiteOpenHelper(context);
}

MySQLiteOpenHelper is a simple extension of SQLiteOpenHelper.

The failure is happening on (again, this is inside by db class):

        database = dbHelper.getWritableDatabase();

Obviously I don't really want to recreate a connection every time in my App - and I think nobody would want that? Which makes me think there should be a way to do this properly in Robolectric and I'm just missing a trick here?

EDIT:

Also, the test runs successfully in isolation, which makes me think it's something to do with Robolectric moving between the test cases and reusing the database connection?

Both tests don't do ANYTHING specific to database or related to any of the DB classes. First test starts a fragment which will access the database to write some data. Second test is failing on attempt to open the db as above.

like image 443
vkislicins Avatar asked Feb 11 '23 02:02

vkislicins


2 Answers

Reset all singleton instances between each test or you will get side effects like yours.

@After
public void finishComponentTesting() {
    resetSingleton(YourSQLiteOpenHelper.class, "sInstance");
}

private void resetSingleton(Class clazz, String fieldName) {
    Field instance;
    try {
        instance = clazz.getDeclaredField(fieldName);
        instance.setAccessible(true);
        instance.set(null, null);
    } catch (Exception e) {
        throw new RuntimeException();
    }
}
like image 195
nenick Avatar answered Feb 15 '23 09:02

nenick


IMO you have to remove/replace database usage/singlethons with dependency injection and mock them in tests. In this case you don't need to instantiate things that are not used in your code/tests.

Sounds like dummy suggestion and something that requires more effort than "just to fix current state". But my experience it is worth to do and it will lead for clear design and testing for entire application.

As for me it is comparable to (sorry again for obvious examples):

  1. Debug vs Unit testing
  2. Fixing OEM with preventing memory leaks
like image 29
Eugen Martynov Avatar answered Feb 15 '23 09:02

Eugen Martynov