Room Migrations — “Testing”

Nataraj KR
2 min readDec 29, 2021

Changing database configuaration as the app grows is inevitable. Room supports inceremental migration. Make changes to the database and then increase the version, the app is good to go.

With “fallbackToDestructiveMigration()” Room database clears itself when it recognises the version change so that there are no conflicts. This is not ideal as the persisted data of the user gets lost. Hence room is provided with abstract class “Migration” which can be extended to handle migrations. So far it is great, the actual problem is testing the database after the migrations are done.

Uninstalling the latest apk to install the old apk and then installing the latest apk with the DB changes procedure can be used to test the migrations. But it is very time consuming and not efficient. To the rescue is the “room-testing” maven artifact. This maven artifact from room assists in testing the database migrations effortlessly.

To begin with enable room to export database schema into a JSON file at compile time using.

android {
...
kapt {
arguments {
arg("room.schemaLocation", "$projectDir/schemas")
}
}
sourceSets {
// Adds exported schema location as test app assets.
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
}

dependencies {
...
androidTestImplementation "androidx.room:room-testing:2.4.0"
}
// put this inside the app's build.gradle

These schema files are like history of the database; they help room in creating a specific version of database. So add them as assets to the androidTest app. Then add “room-testing” artifact with androidTestImplementation configuration.

The testing package provides a MigrationTestHelper class, which can read exported schema files. The package also implements the JUnit4 TestRule interface, so it can manage created databases.

Create a test in the androidTest directory to run the test in the testApp.

To test a single migration:

@RunWith(AndroidJUnit4::class)
class MigrationTest {
private val TEST_DB = "migration-test"

@get:Rule
val helper: MigrationTestHelper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
MigrationDb::class.java.canonicalName,
FrameworkSQLiteOpenHelperFactory()
)

@Test
@Throws(IOException::class)
fun migrate1To2() {
var db = helper.createDatabase(TEST_DB, 1).apply {
// db has schema version 1. insert some data using SQL queries.
// You cannot use DAO classes because they expect the latest schema.
execSQL(...)

// Prepare for the next version.
close()
}

// Re-open the database with version 2 and provide
// MIGRATION_1_2 as the migration process.
db = helper.runMigrationsAndValidate(TEST_DB, 2, true, MIGRATION_1_2)

// MigrationTestHelper automatically verifies the schema changes,
// but you need to validate that the data was migrated properly.
}
}

To test all migrations use:

@RunWith(AndroidJUnit4::class)
class MigrationTest {
private val TEST_DB = "migration-test"

// Array of all migrations
private val ALL_MIGRATIONS = arrayOf(
MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)

@get:Rule
val helper: MigrationTestHelper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java.canonicalName,
FrameworkSQLiteOpenHelperFactory()
)

@Test
@Throws(IOException::class)
fun migrateAll() {
// Create earliest version of the database.
helper.createDatabase(TEST_DB, 1).apply {
close()
}

// Open latest version of the database. Room will validate the schema
// once all migrations execute.
Room.databaseBuilder(
InstrumentationRegistry.getInstrumentation().targetContext,
AppDatabase::class.java,
TEST_DB
).addMigrations(*ALL_MIGRATIONS).build().apply {
openHelper.writableDatabase.close()
}
}
}

This migration testing mechanism is robust & saves a ton of time.

#peace✌🏻

Happy coding.

ref: https://developer.android.com/training/data-storage/room/migrating-db-versions

--

--