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.

You can learn more about testing in the "Testing Your Changes" chapter of Elements of Android Jetpack!

Writing Instrumented Tests

On the whole, writing instrumented 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 instrumented test case class to exercise the NoteDatabase:

package com.commonsware.room.notes

import androidx.room.Room
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.natpryce.hamkrest.assertion.assertThat
import com.natpryce.hamkrest.equalTo
import com.natpryce.hamkrest.hasSize
import com.natpryce.hamkrest.isEmpty
import org.junit.Test
import org.junit.runner.RunWith
import java.util.*

@RunWith(AndroidJUnit4::class)
class NoteStoreTest {
  private val db = Room.inMemoryDatabaseBuilder(
    InstrumentationRegistry.getInstrumentation().targetContext,
    NoteDatabase::class.java
  )
    .build()
  private val underTest = db.notes()

  @Test
  fun insertAndDelete() {
    assertThat(underTest.loadAll(), isEmpty)

    val entity = NoteEntity(
      id = UUID.randomUUID().toString(),
      title = "This is a title",
      text = "This is some text",
      version = 1
    )

    underTest.insert(entity)

    underTest.loadAll().let {
      assertThat(it, hasSize(equalTo(1)))
      assertThat(it[0], equalTo(entity))
    }

    underTest.delete(entity)

    assertThat(underTest.loadAll(), isEmpty)
  }

  @Test
  fun update() {
    val entity = NoteEntity(
      id = UUID.randomUUID().toString(),
      title = "This is a title",
      text = "This is some text",
      version = 1
    )

    underTest.insert(entity)

    val updated = entity.copy(title = "This is new", text = "So is this")

    underTest.update(updated)

    underTest.loadAll().let {
      assertThat(it, hasSize(equalTo(1)))
      assertThat(it[0], equalTo(updated))
    }
  }
}

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 NoteStoreTest class — is to use an in-memory database. The db property is initialized using Room.inMemoryDatabaseBuilder(), so we get our fast, disposable in-memory database. For a context, we use InstrumentationRegistry.getInstrumentation().targetContext, a Context for the code being tested. We then set up underTest to be the object that we are testing: the NoteStore and its functions.

There are two key advantages for using an in-memory database for instrumented testing:

  1. It is intrinsically self-contained. Once the NoteDatabase is closed (or garbage-collected), its memory is released, and if separate tests use separate NoteDatabase instances, one will not affect the other.
  2. 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 instrumented 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.

The Test Functions

Our test functions do things like:

And, along the way, we use Hamkrest matchers to confirm that everything is working as we expect.


Prev Table of Contents Next

This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.