Testing Migrations
It would be nice if your migrations worked. Users, in particular, appreciate working code… or, perhaps more correctly, get rather angry with non-working code.
Hence, you might want to test the migrations.
This gets a bit tricky, though. The code-generated Room classes are expecting the latest-and-greatest schema version, so you cannot use your DAO for testing older schemas. Besides, RoomDatabase.Builder
wants to set up your database with that latest-and-greatest schema automatically.
Fortunately, Room ships with some testing code to help you test your schemas in isolation… though you bypass most of Room to do that.
Adding the Artifact
This testing code is in a separate android.arch.persistence.room:testing
artifact, one that you can add via androidTestCompile
to put in your instrumentation tests but leave out of your production code:
dependencies {
implementation "com.android.support:recyclerview-v7:28.0.0"
implementation 'com.android.support:support-fragment:28.0.0'
androidTestImplementation 'com.android.support:support-compat:28.0.0'
androidTestImplementation 'com.android.support:support-core-utils:28.0.0'
implementation "android.arch.persistence.room:runtime:1.1.1"
annotationProcessor "android.arch.persistence.room:compiler:1.1.1"
androidTestImplementation "com.android.support:support-annotations:28.0.0"
androidTestImplementation 'com.android.support.test:rules:1.0.2'
androidTestImplementation "android.arch.persistence.room:testing:1.1.1"
}
Adding the Schemas
Remember those exported schemas? While we used them for helping us write the migrations, their primary use is for this testing support code.
By default, those schemas are stored outside of anything that goes into your app. After all, you do not need those JSON files cluttering up your production builds. However, this also means that those schemas are not available to your test code, by default.
However, we can fix that, by adding those schemas to the assets/
used in the androidTest
source set, by having this closure in your android
closure of your module’s build.gradle
file:
sourceSets {
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
Here, "$projectDir/schemas".toString()
is the same value that we used for the room.schemaLocation
annotation processor argument. This snippet tells Gradle to include the contents of that schemas/
directory as part of our assets/
.
The result is that our instrumentation test APK will have those directories named after our RoomDatabase
classes (e.g., com.commonsware.android.room.TripDatabase/
) in the root of assets/
. If you have code that uses assets/
, make sure that you are taking steps to ignore these extra directories.
Creating and Using a MigrationTestHelper
The testing support comes in the form of a MigrationTestHelper
that you can employ in your instrumentation tests.
Adding the Rule
MigrationTestHelper
is a JUnit4 rule, which you add to your test case class via the @Rule
annotation:
@Rule
public MigrationTestHelper helper;
Setting Up the Helper
You then need to create an instance of the MigrationTestHelper
, such as in a @Before
-annotated method:
@Before
public void setUp() {
helper=new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
TripDatabase.class.getCanonicalName());
}
MigrationTestHelper
takes two parameters, both of which are a bit unusual.
First, it takes an Instrumentation
object. We use those in our test code, but it is rare that we pass them as a parameter. You get your Instrumentation
usually by calling getInstrumentation()
on the InstrumentationRegistry
.
Then, it takes what appears to be the fully-qualified class name of the RoomDatabase
whose migrations we wish to test. Technically speaking, this is actually the relative path, inside of assets/
, where the schema JSON files are for this particular RoomDatabase
. Given the above configuration, each database’s schemas are put into a directory named after the fully-qualified class name of the RoomDatabase
, which is why this works. However, if you change the configuration to put the schemas somewhere else in assets/
, you would need to modify this parameter to match.
Creating a Database for a Schema Version
There are two main methods on MigrationTestHelper
that we will use in testing. One is createDatabase()
. This creates the database, as a specific database file, for a specific schema version… including any of our historical ones found in those schema JSON files. Here, we ask the helper to create a database named DB_NAME
for schema version 1:
SupportSQLiteDatabase db=helper.createDatabase(DB_NAME, 1);
As part of testing a migration, you will need to add some sample data to the database, using whatever schema you asked to be used, so that you can confirm that the migration worked as expected and did not wreck the existing data. This code will not be very Room-ish, but more like classic SQLite Android programming:
SupportSQLiteDatabase db=helper.createDatabase(DB_NAME, 1);
db.execSQL("INSERT INTO trips (id, title, duration) VALUES (1, NULL, 0)");
final Cursor firstResults=db.query("SELECT COUNT(*) FROM trips");
assertEquals(1, firstResults.getCount());
firstResults.moveToFirst();
assertEquals(1, firstResults.getInt(0));
firstResults.close();
db.close();
Testing a Migration
The other method of note on MigrationTestHelper
is runMigrationsAndValidate()
. After you have set up a database in its starting conditions via createDatabase()
and CRUD operations, runMigrationsAndValidate()
will migrate that database from its original schema version to the one that you specify:
db=helper.runMigrationsAndValidate(DB_NAME, 2, true,
Migrations.FROM_1_TO_2);
You need to supply the same database name (DB_NAME
), a higher schema version (2
), and the specific Migration
that you want to use (Migration.FROM_1_TO_2
).
Not only does this method perform the migration, but it validates the resulting schema against what the entities have set up for that schema version, based on the schema JSON files. If there is something wrong — your migration forgot a newly-added column, for example — your test will fail with an assertion violation. The true
parameter shown above determines whether this schema validation will be checked for un-dropped tables. true
means that if you have unnecessary tables in the database, the test fails; false
means that unnecessary tables are fine and will be ignored.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.