Testing Room
Once you have a RoomDatabase
and its associated DAO(s) and entities set up, you should start testing it.
The good news is that testing Room is not dramatically different than is testing anything else in Android. Room has a few characteristics that make it a bit easier than some things to test, as it turns out.
Writing Instrumentation Tests
On the whole, writing instrumentation tests for Room — where the tests run on an Android device or emulator — is unremarkable. You get an instance of your RoomDatabase
subclass and exercise it from there.
So, for example, here is an instrumentation test case class to exercise the TripDatabase
from the preceding chapter:
package com.commonsware.android.room;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
@RunWith(AndroidJUnit4.class)
public class TripTests {
TripDatabase db;
TripStore store;
@Before
public void setUp() {
db=TripDatabase.create(InstrumentationRegistry.getTargetContext(), true);
store=db.tripStore();
}
@After
public void tearDown() {
db.close();
}
@Test
public void basics() {
assertEquals(0, store.selectAll().size());
final Trip first=new Trip("Foo", 2880);
assertNotNull(first.id);
assertNotEquals(0, first.id.length());
store.insert(first);
assertTrip(store, first);
final Trip updated=new Trip(first.id, "Foo!!!", 1440);
store.update(updated);
assertTrip(store, updated);
store.delete(updated);
assertEquals(0, store.selectAll().size());
}
private void assertTrip(TripStore store, Trip trip) {
List<Trip> results=store.selectAll();
assertNotNull(results);
assertEquals(1, results.size());
assertTrue(areIdentical(trip, results.get(0)));
Trip result=store.findById(trip.id);
assertNotNull(result);
assertTrue(areIdentical(trip, result));
}
private boolean areIdentical(Trip one, Trip two) {
return(one.id.equals(two.id) &&
one.title.equals(two.title) &&
one.duration==two.duration);
}
}
Here, we:
- Create an empty database
- Get the DAO (
TripStore
) - Confirm that there are no trips in the database
- Create a
Trip
object andinsert()
it into the database, then confirm that the database was properly inserted - Create a new
Trip
object with the same ID as the first,update()
the database using it, then confirm that the database was properly modified - Delete the
Trip
object, then confirm that the database has no trips once again
Using In-Memory Databases
When testing a database, though, one of the challenges is in making those tests “hermetic”, or self-contained. One test method should not depend upon another test method, and one test method should not affect the results of another test method accidentally. This means that we want to start with a known starting point before each test, and we have to consider how to do that.
One approach — the one taken in the above TripTests
class — is to use an in-memory database. The static create()
method on TripDatabase
, if you pass true
for the second parameter, creates a TripDatabase
backed by memory, not disk.
There are two key advantages for using an in-memory database for instrumentation testing:
- It is intrinsically self-contained. Once the
TripDatabase
is closed, its memory is released, and if separate tests use separateTripDatabase
instances, one will not affect the other. - Reading and writing to and from memory is much faster than is reading and writing to and from disk, so the tests run much faster.
On the other hand, this means that the instrumentation tests are useless for performance testing, as (presumably) your production app will actually store its database on disk. You could use Gradle command-line switches, custom build types and buildConfigField
, or other means to decide when tests are run whether they should use memory or disk.
Importing Starter Data
The one downside to having an empty starter database, such as a fresh in-memory database, is that you have no data. Eventually, you need some data to test.
That could come from test code, such as what TripTests
does. In many cases, this is a necessary part of testing, to confirm that all of your DAO methods work as expected.
Alternatives include:
- Loading the data from some neutral format (e.g., JSON) via some utility method
- Packaging one or more starter database as assets in the instrumentation tests (e.g.,
src/androidTest/assets/
), then usingATTACH DATABASE ...
andINSERT INTO ... SELECT FROM ...
SQLite code to copy from the starter database to the database to be used in testing
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.