Image for post
Image for post
Is everything where it’s supposed to be? Test migrations. (Photo by Dmitri Popov on Unsplash)

Testing Room migrations

Florina Muntenescu
Jul 24, 2017 · 5 min read

In a previous post I explained how database migrations with Room work under the hood. We saw that an incorrect migration implementation can lead to either your app crashing or loss of the user’s data.

On top of this, the SQL statements you execute in the Migration.migrate method are not checked at compile time, opening the door to more issues. Knowing all of this, testing the migrations becomes an essential task. Room provides the MigrationTestHelper test rule which allows you to:

  • Create a database at a given version

Note that Room will not validate the data in the database. This is something you need to implement yourself.

Here’s what you need to know to test Room migrations.

Under the hood

To test migrations, Room needs to know several things about your current database version: the version number, the entities, the identity hash and the queries for creating and updating the room_master_table. All of these are automatically generated by Room at compile time and stored in a schema JSON file.

In your build.gradle file you specify a folder to place these generated schema JSON files. As you update your schema, you’ll end up with several JSON files, one for every version. Make sure you commit every generated file to source control. The next time you increase your version number again, Room will be able to use the JSON file for testing.

Prerequisites

To enable the generation of the JSON file, update your build.gradle file with the following:

  1. Define the schema location
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments += ["room.schemaLocation":
"$projectDir/schemas".toString()]
}
}
}

2. Add the schema location to the source sets

android {

sourceSets {
androidTest.assets.srcDirs +=
files("$projectDir/schemas".toString())
}

3. Add the room testing library to the list of dependencies

dependencies {androidTestImplementation    
“android.arch.persistence.room:testing:1.0.0-alpha5”
}

Migration test rule

Creating the database, schemas, opening and closing the database, running migrations — that’s a lot of boilerplate code that you would need to write for almost every test. To avoid writing all of this, use the MigrationTestHelper test rule in your migration test class.

@Rule
public MigrationTestHelper testHelper =
new MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
<your_database_class>.class.getCanonicalName(),
new FrameworkSQLiteOpenHelperFactory());

The MigrationTestHelper heavily relies on the generated JSON file, both for creating the database and for validating the migration.

You are able to create your database in a specific version:

// Create the database with version 2
SupportSQLiteDatabase db =
testHelper.createDatabase(TEST_DB_NAME, 2);

You can run a set of migrations and validate automatically that the schema was updated correctly:

db = testHelper.runMigrationsAndValidate(TEST_DB_NAME, 4, validateDroppedTables, MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4);

Implement tests

The testing strategy is simple:

  1. Open the database in a specific version

For example, version 3 of the database adds a new column: date. So, when testing the migration from version 2 to version 3, we check the validity of the data that was inserted in version 2, but also the default value for our new column. Here’s how our AndroidJUnitTest looks like:

@Test
public void migrationFrom2To3_containsCorrectData() throws
IOException {
// Create the database in version 2
SupportSQLiteDatabase db =
testHelper.createDatabase(TEST_DB_NAME, 2);
// Insert some data
insertUser(USER.getId(), USER.getUserName(), db);
//Prepare for the next version
db.close();

// Re-open the database with version 3 and provide MIGRATION_1_2
// and MIGRATION_2_3 as the migration process.
testHelper.runMigrationsAndValidate(TEST_DB_NAME, 3,
validateDroppedTables, MIGRATION_1_2, MIGRATION_2_3);

// MigrationTestHelper automatically verifies the schema
//changes, but not the data validity
// Validate that the data was migrated properly.
User dbUser = getMigratedRoomDatabase().userDao().getUser();
assertEquals(dbUser.getId(), USER.getId());
assertEquals(dbUser.getUserName(), USER.getUserName());
// The date was missing in version 2, so it should be null in
//version 3
assertEquals(dbUser.getDate(), null);
}

Testing the migration from an SQLiteDatabase implementation to Room

I talked before about the steps you need to take to go from a standard SQLiteDatabase implementation to Room, but I didn’t go into details on how to test the migration implementation.

Since the initial database version was not implemented using Room, we don’t have the corresponding JSON file, therefore we cannot use the MigrationTestHelper to create the database.

Here’s what we need to do:

  1. Extend the SQLiteOpenHelper class and, in onCreate, execute the SQL queries that create your database tables.

Database version 1 was implemented using SQLiteDatabase API, then in version 2 we migrated to Room and, in version 3 we added a new column. A test checking a migration from version 1 to 3 looks like this:

@Test
public void migrationFrom1To3_containsCorrectData() throws IOException {
// Create the database with the initial version 1 schema and
//insert a user
SqliteDatabaseTestHelper.insertUser(1, USER.getUserName(), sqliteTestDbHelper);

// Re-open the database with version 3 and provide MIGRATION_1_2
// and MIGRATION_2_3 as the migration process.
testHelper.runMigrationsAndValidate(TEST_DB_NAME, 3, true,
MIGRATION_1_2, MIGRATION_2_3);

// Get the latest, migrated, version of the database
// Check that the correct data is in the database
User dbUser = getMigratedRoomDatabase().userDao().getUser();
assertEquals(dbUser.getId(), 1);
assertEquals(dbUser.getUserName(), USER.getUserName());
// The date was missing in version 2, so it should be null in
//version 3
assertEquals(dbUser.getDate(), null);
}

Show me the code

You can check out the implementation in this sample app. To ease the comparison every database version was implemented in its own flavor:

  1. sqlite — Uses SQLiteOpenHelper and traditional SQLite interfaces.

Conclusion

With Room, implementing and testing migrations is easy. The MigrationTestHelper test rule allows you to to open your database at any version, to run migrations and validate schemas in only a few lines of code.

Have you started using Room and implemented migrations? If so, let us know how it went in the comments below.

Android Developers

The official Android Developers publication on Medium

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store