How to write Robolectric (2.3) test using database - android

Due to last release of Robolectic to version 2.3, it's written that (https://github.com/robolectric/robolectric/releases):
Robolectric now uses a real implementation of SQLite instead of a collection of shadows and fakes. Tests can now be written to verify real database behavior.
I haven't found any "How to" documentation.
I'd like to know how should I implement test on e.g. Activity using SQLiteDatabase query. Where should I put .db file so a test uses it.

You will need to put the .db file under src/test/resources/ folder.
For example, sample.db
Then in your unit test setUp() call:
#Before
public void setUp() throws Exception {
String filePath = getClass().getResource("/sample.db").toURI().getPath();
SQLiteDatabase db = SQLiteDatabase.openDatabase(
(new File(filePath)).getAbsolutePath(),
null,
SQLiteDatabase.OPEN_READWRITE);
// perform any db operations you want here
}

Here is an example how to test database, oprations.
// just a wrapper for the content values map
Agenda agenda = new Agenda();
agenda.setName("MyAgenda");
agenda.setDate("current date");
long rowId = agendaManager.insert(agenda); // the guy who makes database operations
Cursor query = context.getContentResolver().query(AgendaProvider.AGENDA_CONTENT_URI, null, null, null, null);
assertThat(query.getCount()).isEqualTo(1);
query.moveToNext();
Agenda dbAgenda = new Agenda(query);
assertThat(dbAgenda.getRowId()).isPositive();
assertThat(dbAgenda.getRowId()).isEqualTo(rowId);
assertThat(dbAgenda.getName()).isEqualTo(agenda.getName());
assertThat(dbAgenda.getDate()).isEqualTo(agenda.getDate());
An more detailed example may be found here https://github.com/nenick/android-gradle-template/blob/master/UnitTestsRobolectric/src/test/java/com/example/managers/AgendaManagerTest.java

Related

Android Room - Verify the correctness of DB indices during migration unit test

We just begin expose to migration unit test code. We love it as it helps us to identify few bugs.
It is useful to help us verify the expected table structure.
But, I notice that the migration code, unable to check against whether indices are being created correctly. If I purposely left out the index creation code from the migration, the unit test is still pass.
I notice in the generated schema json files, they contain indices information. Hence, I expect the migration test code should able to capture the missing indices.
Here's our migration test code.
#RunWith(AndroidJUnit4.class)
public class MigrationTest {
private static final String TEST_DB = "migration-test";
// Array of all migrations
private static final Migration[] ALL_MIGRATIONS = new Migration[]{
new Migration_1_2(),
new Migration_2_3(),
new Migration_3_4(),
new Migration_4_5(),
new Migration_5_6(),
new Migration_6_7(),
new Migration_7_8(),
new Migration_8_9(),
new Migration_9_10(),
new Migration_10_11(),
new Migration_11_12(),
new Migration_12_13(),
new Migration_13_14(),
new Migration_14_15(),
new Migration_15_16(),
new Migration_16_17(),
new Migration_17_18(),
new Migration_18_19(),
new Migration_19_20(),
new Migration_20_21(),
new Migration_21_22(),
new Migration_22_23(),
new Migration_23_24()
};
#Rule
public MigrationTestHelper helper;
public MigrationTest() {
helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
MyAppRoomDatabase.class.getCanonicalName(),
new FrameworkSQLiteOpenHelperFactory());
}
#Test
public void migrateAll() throws IOException {
// Create earliest version of the database.
SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);
db.close();
// Open latest version of the database. Room will validate the schema
// once all migrations execute.
MyAppRoomDatabaseappDb = Room.databaseBuilder(
InstrumentationRegistry.getInstrumentation().getTargetContext(),
MyAppRoomDatabase.class,
TEST_DB)
.addMigrations(ALL_MIGRATIONS).build();
appDb.getOpenHelper().getWritableDatabase();
appDb.close();
}
}
Is there any way we can do, to verify the correctness of the DB indices?
You could either query table sqlite_master directly - or use the PRAGMA extension.
This would be PRAGMA index_list(tablename) & PRAGMA index_info(indexname); there's also PRAGMA schema.index_xinfo(indexname). I'm not aware of any less verbose way to check for that, as androidx.room:room-testing doesn't seem to support that. However, when wrapping such a query into a test-method (eg. taking table-name and index-name as arguments), this still might be convenient enough to consider it as acceptable. The SQLite FAQ also describe that.
The source-code of class MigrationTestHelper confirms that method onValidateSchema() (line 430) does not query for WHERE type='index'. You still could file a feature request on the issue tracker: https://issuetracker.google.com/issues/new?component=413107&template=1096568
They'd be in the position to provide an authoritative answer.
The reason why the index is not being verified, is that I am using Local unit test - https://developer.android.com/training/testing/unit-testing/local-unit-tests
However, Robolectric has problem in emulating the correct behaviour
It is safer to use Instrument unit test - https://developer.android.com/training/testing/unit-testing/instrumented-unit-tests
This is being mentioned in https://developer.android.com/training/data-storage/room/testing-db#test
Here's some explanation from Room dev team - https://issuetracker.google.com/issues/176457306

moving from android sqlcipher to android sqlite database

From the last 3 days i am trying to upgrade my database to a higher version of SQLCipher library (v3.1.0). I did every step and followed a few tutorials too. But keep on getting the error "File is encrypted or not a Database". Now am trying to move to unencrypted database ie. simple sqlite database.
Do we have a way to move to encrypted database to un-encrypted database? Thanks in advance.
This is the code i am working on:
public MyDBAdapter(Context context) {
this.context = context;
File dbFile = context.getDatabasePath(DATABASE_NAME);
String dbPath = context.getDatabasePath(DATABASE_NAME).toString();
if (dbFile.exists()) {
try {
SQLiteDatabase.loadLibs(context.getApplicationContext());//load SqlCipher libraries
SQLiteDatabase db = getExistDataBaseFile(dbPath, KEY_PASSPHRASE_ENCRYPTION, dbFile);
if (version == 1) {
MigrateDatabaseFrom1xFormatToCurrentFormat(
dbFile, KEY_PASSPHRASE_ENCRYPTION);
}
System.out.println("Old Database found and updated.");
} catch (Exception e) {
System.out.println("No Old Database found");
}
}
this.dbhelper = new MyDBHelper(this.context, DATABASE_NAME, null,
DATABASE_Version);
db = dbhelper.getWritableDatabase(KEY_PASSPHRASE_ENCRYPTION);
}
private SQLiteDatabase getExistDataBaseFile(String FULL_DB_Path, String password, File dbFile) {// this function to open an Exist database
SQLiteDatabase.loadLibs(context.getApplicationContext());
SQLiteDatabaseHook hook = new SQLiteDatabaseHook() {
public void preKey(SQLiteDatabase database) {
System.out.println("-----Inside preKey");
}
public void postKey(SQLiteDatabase database) {
System.out.println("-----Inside postKey");
database.rawExecSQL("PRAGMA cipher_migrate;");
}
};
SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(
dbFile, "Test123", null, hook); // Exception
return database;
}
If you are upgrading your SQLCipher library to the latest version, currently at 3.1.0, and your previous version was 2.x (as you mentioned in the comments above), you will need to upgrade the database file format as well. One of the big changes in the 3.x release was an increase in key derivation length, from 4,000 to 64,000. If you are using all of the standard SQLCipher configurations, upgrading the database format is straight forward. We have included a new PRAGMA call cipher_migrate that will perform this operation for you. You can execute this within the postKey event of the SQLiteDatabaseHook which is to be provided in your call to SQLiteDatabase.openOrCreateDatabase. An example of this can be found in the SQLCipher for Android test suite here.

System error android sqlite insert

I am trying to build a sqlite database. It has 2800 entries in them. When i try to insert, it takes a minute or so and later gives the system error message. The respective codes are given below.
In the create database java file,
ContentValues cv4 = new ContentValues();
public long createVariantEntry(String varid, String makeid, String modelid, String varname, String posteddate) {
cv4.put(VARIANT_ID, varid);
cv4.put(VARIANT_MAKE_ID, makeid);
cv4.put(VARIANT_MODEL_ID, modelid);
cv4.put(VARIANT_NAME, varname);
cv4.put(VARIANT_POSTED_DATE, posteddate);
return Database.insert(VARIANT_TABLE_NAME, null, cv4);
}
In the mainActivity,
for(int i = 0; i<build_emp_database.size();i++)
{
md.createVariantEntry(build_emp_database.get(i).get(0), build_emp_database.get(i).get(1), build_emp_database.get(i).get(2), build_emp_database.get(i).get(3), build_emp_database.get(i).get(4));
}
Also, just for 2800 entries, it is taking more than a minute, is there any way to speed them up?? I have several small databases, and have loaded them successfully. This is the only big database and its creating an issue while inserting. Please help where am i going wrong.
Don't try to run the insert operation on the UI thread, for starters.
I suggest you investigate using a content provider as a wrapper around your database. The ContentResolver object provides methods for doing operations in batch, and is in general a more robust way of working with databases. Use an IntentService to run the insert operation in the background.
If you do a lot insert operation, you need use the ContentProviderOperation to optimize your db operation. like these:
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
for(int i = 0; i<build_emp_database.size();i++) {
ContentProviderOperation.Builder builder =
ContentProviderOperation.newInsert(yourUrl);
builder.withValue(VARIANT_ID, varid)
.withValue(VARIANT_MAKE_ID, makeid)
...
ops.add(builder.build());
}
yourContentResolver.applyBatch(yourauthority, ops);

Robolectric: Testing with ormlite

I'm trying to test ORMLite DAOs with robolectric, but database behaviour is not the same as when it's used from my android app. My DAOs are working perfectly well on the android application.
Reading about robolectric shadows and debugging code, I encountered ShadowSQLiteOpenHelper (code here).
Does anyone know if this Shadow is enough to test ormlite daos? Or I have to create my own shadow to achieve that? Any clue/tip/suggestion/example here?
Thanks in advance.
Extra info:
Test method:
#Test
public void basicTest() throws SQLException {
assertNotNull(randomStringResource); // Injection of an android resource: OK
assertThat(randomStringResource, equalTo("Event")); // With correct value: OK
assertNotNull(eventDao); // Dao injection: OK
assertThat(eventDao.countOf(), equalTo(0L)); // Table empty: OK
Event e1 = new Event("e1", new Date());
eventDao.create(e1);
assertNotNull(e1.getId()); // ID generated by OrmLite: OK
assertThat(eventDao.countOf(), equalTo(1L)); // Table not empty: OK
assertThat("e1", equalTo(eventDao.queryForId(e1.getId()).getName())); // Query for inserted event: Throws exception
}
Some of the problems encountered running this test:
Errors querying entities with "camelCased" property names: error thrown at last line of test (related problem). Never had a problem like this running the android app.
When I changed one of these properties name (e.g., isEnabled to enabled) in order to avoid the camelCase problem, the previous error persisted... seems like memory database didn't apply the changes that I made on the entity.
Versions used:
Robolectric 1.1
OrmLite 4.41
Sorry for resurrecting your topic but I ran into the same problem.
I'm using OrmLite 4.45 and Robolectric 2.1.
In ShadowSQLiteCursor.java, cacheColumnNames method calls toLowerCase on each column name. So I decided to extend ShadowSQLiteCursor with my own (which doesn't call toLowerCase):
/**
* Simulates an Android Cursor object, by wrapping a JDBC ResultSet.
*/
#Implements(value = SQLiteCursor.class, inheritImplementationMethods = true)
public class ShadowCaseSensitiveSQLiteCursor extends ShadowSQLiteCursor {
private ResultSet resultSet;
public void __constructor__(SQLiteCursorDriver driver, String editTable, SQLiteQuery query) {}
/**
* Stores the column names so they are retrievable after the resultSet has closed
*/
private void cacheColumnNames(ResultSet rs) {
try {
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
columnNameArray = new String[columnCount];
for(int columnIndex = 1; columnIndex <= columnCount; columnIndex++) {
String cName = metaData.getColumnName(columnIndex);
this.columnNames.put(cName, columnIndex - 1);
this.columnNameArray[columnIndex - 1] = cName;
}
} catch(SQLException e) {
throw new RuntimeException("SQL exception in cacheColumnNames", e);
}
}
}
My answer obviously comes too late but may help others!

How to access one class database in another class?

I created a database in my class like
public final String CRAZY_ALARM_DB_NAME = "CrazyDb";
public final String CRAZY_ALARM_TABLE_NAME = "CrazyTable";
alarmDB = this.openOrCreateDatabase(
CRAZY_ALARM_DB_NAME, MODE_PRIVATE, null
);
alarmDB.execSQL("CREATE TABLE IF NOT EXISTS "
+ CRAZY_ALARM_TABLE_NAME
+" (REQ_CODE INT(3),DAY INT(3),HOUR INT(3)"
+",MINUTE INT(3),COUNT INT(3),REPEAT INT(3)"
+",DAYS VARCHAR2(100),SUN INT(3),MON INT(3),"
+"TUE INT(3),WED INT(3),THU INT(3),FRI INT(3),"
+"SAT INT(3));"
);
cr = alarmDB.rawQuery("SELECT * FROM "
+CRAZY_ALARM_TABLE_NAME, null
);
so i want to use this database in another class. I am also do the same thing ,i wrote "openorcreate "code in another class and also cursor..
but it gave an exception like no such table while compiling ... at cursor line..
please help me.
You should use an SQLiteOpenHelper-class to access and maintain your Database.
If you do so, you can (in whatever class you like) use the following lines to get a read or writable database:
YourSQliteOpenHelper db_con = new YourSQliteOpenHelper(getApplicationContext());
// Readable:
SQLiteDatabase db = db_con.getReadableDatabase();
// Writeable:
SQLiteDatabase db = db_con.getWritableDatabase();
A tutorial on how to use the SQLiteOpenHelper can be found here.
The best you could do is to have a database helper where you could have all these calls and which could be available and accessible by all your activities.
Moreover, you should remove and install again your app in order to be able to create the table. I have this problem sometimes.

Categories

Resources