Android Room Database Migration Instrumented Unit Test (Kotlin)

Edit Module build.gradle.

android {
    defaultConfig {
        ...

        javaCompileOptions {
            annotationProcessorOptions {
                arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
            }
        }
    }

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

dependencies {
    // existing
    implementation "android.arch.persistence.room:runtime:$android_room_version"
    kapt "android.arch.persistence.room:compiler:$android_room_version"

    // new
    androidTestImplementation "android.arch.persistence.room:testing:$android_room_version"
}

NOTE: Make sure exportSchema = true for @Database, else schema json won't be generated and later you might bump into FileNotFoundException: Cannot find the schema file in the assets folder.

@Database(entities = [(Pin::class)], version = 1, exportSchema = true)@TypeConverters(RoomConverters::class)abstract class AppDatabase : RoomDatabase() {    abstract fun pinDao(): PinDao}

When you compile the app, assets/[DatabaseClassPath] (Android view) or /app/schemas/[DatabaseClassPath] (Project view) is generated with 1.json, 2.json, etc. (the number is the database version).

Perform Android Room database migration.

NOTE: You might want to generate 1.json by performing a compilation first, then only perform the necessary database migration (add new column/table to @Entity and change database version) to generate 2.json.

Create MigrationTest.kt in androidTest.

@RunWith(AndroidJUnit4::class)class MigrationTest {    companion object {        private const val TAG = "MigrationTest"        private const val TEST_DB = "test-db"    }    lateinit var db: SupportSQLiteDatabase    @Rule @JvmField    val helper: MigrationTestHelper = MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),            AppDatabase::class.java!!.canonicalName,            FrameworkSQLiteOpenHelperFactory())    @Before    fun setUp() {        db = helper.createDatabase(TEST_DB, 1)    }    @Test    fun migrate1To2() {        // db has schema version 1. insert some data using SQL queries.        // You cannot use DAO classes because they expect the latest schema.        // db.execSQL(...);        val values = ContentValues()        values.put("id", "test01")        values.put("name", "Test 01")        val id = db.insert("pin", SQLiteDatabase.CONFLICT_REPLACE, values)        // Prepare for the next version.        db.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.        val cursor = db.query("SELECT * FROM pin WHERE rowid = ?", arrayOf(id))        MatcherAssert.assertThat(cursor, Is(notNullValue()))        MatcherAssert.assertThat(cursor.moveToFirst(), Is(true))        val name = cursor.getString("name")        MatcherAssert.assertThat(name, Is("Test 01"))        val isLocationAccurate = cursor.getIntOrNull("is_location_accurate")        // MatcherAssert.assertThat(isLocationAccurate, Is(nullValue()))        MatcherAssert.assertThat(isLocationAccurate, Is(0))    }}

NOTE: Refer to Android Instrumented Unit Test if you are not familiar with Instrumented Unit Test.

NOTE: If you bump into duplicate column name exception, check schema/1.json and schema/2.json to make sure the new column doesn't exist in schema/1.json.

References:

❤️ Is this article helpful?

Buy me a coffee ☕ or support my work via PayPal to keep this space 🖖 and ad-free.

Do send some 💖 to @d_luaz or share this article.

✨ By Desmond Lua

A dream boy who enjoys making apps, travelling and making youtube videos. Follow me on @d_luaz

👶 Apps I built

Travelopy - discover travel places in Malaysia, Singapore, Taiwan, Japan.