17

我正在尝试在我的 Android 应用程序中使用 Robolectric 测试一个简单的 SQLite 数据库。我输入了一些值,但是在读取它们时返回 0 行。

我正在使用 SQLiteOpenHelper 类来访问数据库。

// RequestCache extends SQLiteOpenHelper
RequestCache cache = new RequestCache(activity); 
SQLiteDatabase db = cache.getWritableDatabase();

// Write to DB
ContentValues values = new ContentValues();
values.put(REQUEST_TIMESTAMP, TEST_TIME); 
values.put(REQUEST_URL, TEST_URL);
db.insertOrThrow(TABLE_NAME, null, values);

// Read from DB and compare values      
Vector<Request> matchingRequests = new Vector<Request>();
db = cache.getReadableDatabase();
Cursor cursor = db.query(TABLE_NAME, SEARCH_URL_RETURN_COLUMNS, SEARCH_URL_WHERE, new String[] {url}, null, null, ORDER_BY, null);
int id = 0;

while(cursor.moveToNext()) {
    long timestamp = cursor.getLong(0);
    Request request = new Request(id++);
    request.setUrl(url);
    request.setCreationTimestamp(new Date(timestamp));
    matchingRequests.add(request);
}


// Assert that one row is returned
assertThat(matchingRequests.size(), equalTo(1));  // fails, size() returns 0

在 robolectric 之外调试代码时,这可以按预期工作。我做错了什么还是无法使用 Robolectric 测试 SQlite 数据库?

4

3 回答 3

20

Robolectric 2.3使用 SQLite 的真实实现,而不是影子和假货的集合。现在可以编写测试来验证真实的数据库行为。

于 2014-07-04T16:42:26.350 回答
10

注意:此答案已过时。如果您使用的是 Roboletric 2.X,请参阅https://stackoverflow.com/a/24578332/850787

问题是 Robolectric 的 SQLiteDatabase 仅存储在内存中,因此当您调用 getReadableDatabase 或 getWritableDatabase 时,现有数据库将被新的空数据库覆盖。

我遇到了同样的问题,我发现的唯一解决方案是,如果两次给出相同的上下文,我需要分叉 Robolectric 项目并添加 ShadowSQLiteOpenHelper 来保存数据库。然而,我的 fork 的问题是我必须在给出 contex 时“禁用”close()-function,否则 Connection.close() 会破坏内存中的数据库。我已经为它提出了拉取请求,但它还没有合并到项目中。

但是请随意克隆我的版本,它应该可以解决您的问题(如果我理解正确的话:P)。可以在 GitHub 上找到:https ://github.com/waltsu/robolectric

以下是如何使用修改的示例:

Context c = new Activity(); 
SQLiteOpenHelper helper = new SQLiteOpenHelper(c, "path", null, 1); 
SQLiteDatabase db = helper.getWritableDatabase(); 
// With the db write something to the database 
db.query(...); 
SQLiteOpenHelper helper2 = new SQLiteOpenHelper(c, "path", null, 1); 
SQLitedatabase db2 = helper2.getWritableDatabase(); 
// Now db and db2 is actually the same instance 
Cursor c = db2.query(...) ; // Fetch the data which was saved before 

当然,您不需要创建新的 SQLiteOpenHelper,但这只是将相同的上下文传递给两个不同的 SQLiteOpenHelper 将提供相同数据库的示例。

于 2012-02-04T16:04:02.377 回答
2

接受的答案中链接的代码对我不起作用;它可能已经过时了。或者也许我的设置只是不同。我正在使用 Robolectric 2.4 快照,它似乎不包含 ShadowSQLiteOpenHelper,除非我错过了什么。无论如何,我想出了一个解决方案。这是我所做的:

  1. 我创建了一个名为 ShadowSQLiteOpenHelper 的类,并复制粘贴了上面链接的代码的内容(https://github.com/waltsu/robolectric/blob/de2efdca39d26c5f18a3d278957b28a555119237/src/main/java/com/xtremelabs/robolectric/shadows/ShadowSQLiteOpenHelper。 java ),并修复了导入。
  2. 按照http://robolectric.org/custom-shadows/上关于使用自定义阴影的说明,我用@Config( shadows = { ShadowSQLiteOpenHelper.class } ). 无需自定义测试运行器。
  3. 在我的测试中,我实例化了我的 SQLiteOpenHelper 子类,将 anew Activity()作为 Context 传入(Robolectric 很乐意处理它),并照常使用它。

现在,此时,我注意到在本地创建了一个实际的数据库文件:在第一次运行使用我的 SQLiteOpenHelper 子类的测试后,我在后续测试中不断收到 SQLiteException,因为我的表已经存在;我可以在我的本地存储库中看到一个名为“path”的文件和一个名为“path-journal”的文件。这让我很困惑,因为我的印象是影子类正在使用内存数据库。

事实证明,影子类中的违规行是:

database = SQLiteDatabase.openDatabase( "path", null, 0 );

在 getReadableDatabase() 和 getWriteableDatabase() 中。我知道真正的 SQLiteOpenHelper 可以创建一个内存数据库,在查看了源代码以了解它是如何完成的之后,我将上面的行替换为:

database = SQLiteDatabase.create( null );

之后,一切似乎都正常了。我能够插入行并将它们读回。

希望这对其他人有帮助。

发生在我身上的一件奇怪的事情与这个问题并不完全相关,但可能会进一步帮助别人,那就是我也在使用 jmockit,并且我在同一个班级的一次测试中使用过它;但由于某种原因,这导致我的自定义阴影类无法使用。我只为那门课改用 Mockito,它工作得很好。

于 2014-07-02T08:48:47.490 回答