• 8 heures
  • Difficile

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 27/04/2023

Test your database

Before implementing the SQLite database in our activity TodoListActivity, we will test the various CRUD calls, and verify that they're working correctly. This will also allow us to validate our DAO interfaces with various tests. 🙂

Create a test class

To begin testing our database's tables, we will focus on the class  ItemDAO, and validate most of its CRUD methods. For this, we will create a class called ItemDaoTest in the folder dedicated to our instrumented tests, androidTest/, so that they are run from an Android device (rather than on the JVM).

But first, we must install a small library from Android, which will make it easier to implement our tests. Do this by inputting the command below:

Excerpt of build.gradle
dependencies {
   ...
   // TESTING
   androidTestImplementation "android.arch.core:core-testing:1.1.1"
}

Perfect! Now let’s create a class responsible for the tests carried out on ItemDAO. This test class will be instrumented and run using AndroidJUnitRunner via the annotation  @RunWith(AndroidJUnit4.class). This test runner will load the package containing all of our tests on one Android device to run them:

Class ItemDaoTest.kt:
@RunWith(AndroidJUnit4::class)
class ItemDaoTest {
 
   // FOR DATA
   private lateinit var database: SaveMyTripDatabase = null
 
   @Rule @JvmField
   var instantTaskExecutorRule = InstantTaskExecutorRule()
 
   @Before
   @Throws(Exception::class)
   fun initDb() {
       this.database = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(),
                SaveMyTripDatabase::class.java)
                .allowMainThreadQueries()
                .build()
   }
 
   @After
   @Throws(Exception::class)
   fun closeDb() {
       database.close()
   }
}

Then we defined a rule using the annotation @Rule. As a reminder, a rule allows you to define the manner in which the tests will be executed. In our case, we have used the InstantTaskExecutorRule to force the execution of each test synchronously (i.e., without moving them to a thread in the background).

Next, we created a method initDb(), which will load to create an instance of our database, then place it in the variable  database  declared at the top of our class. This method will be called before each test is run, using the annotation @Before.

 Okay, but that's weird. The builder to generate our Room class is strange. 

Ah! You noticed! 🙂 Yes, to facilitate the unit tests, Room provides us with a builder called inMemoryDatabaseBuilder. This builder creates an instance of our database directly in memory (rather than in a file on a device!). Isn't that handy?

Testing CRUD calls

Now let’s move on to the tests. First, create (in your test package) a utility object that will enable us to more easily run methods that return values of the type LiveData.

Object LiveDataTestUtil.kt
object LiveDataTestUtil {

   @Throws(InterruptedException::class)
   fun <T> getValue(liveData: LiveData<T>: T {
       val data = arrayOfNulls<Any>(1)
       val latch = CountDownLatch(1)
       val observer = object : Observer<T> {
           override fun onChanged(o: T?) {
               data[0] = o
               latch.countDown()
               liveData.removeObserver(this)
           }
       }
       liveData.observeForever(observer)
       latch.await(2, TimeUnit.SECONDS)
 
       return data[0] as T
   }
}

This class is provided by Google to help you create tests involving the type LiveData more easily, and especially to block the test’s execution until the result has been returned.

First, let’s test the adding and retrieval of a new user in our SQLite database.

Excerpt from ItemDaoTest
@RunWith(AndroidJUnit4.class)
class ItemDaoTest {
 
   ...
   // DATA SET FOR TEST
   private val USER_ID: Long = 1
   private val USER_DEMO = User(USER_ID, "Philippe", "https://www.google.fr, ")
   private val NEW_ITEM_PLACE_TO_VISIT = Item("Visit this dream place !", 0, USER_ID)
   private val NEW_ITEM_IDEA = Item("We could do dog sledding ?", 1, USER_ID)
   private val NEW_ITEM_RESTAURANTS = Item("This restaurant looks nice", 2, USER_ID)
 
   @Test
   @Throws(InterruptedException::class)
   fun insertAndGetUser() {
       // BEFORE : Adding a new user
       this.database.userDao().createUser(USER_DEMO)
       // TEST
       val user = LiveDataTestUtil.getValue(this.database.userDao().getUser(USER_ID))
       assertTrue(user.username == USER_DEMO.username && user.id == USER_ID)
   }

Before creating a test, declare and instantiate a dataset that you will likely reuse in your various tests. Here we're simply creating a demo user. 🙂

Next, create the first test with the annotation @Test. This test will first insert a new user into the database (with the DAO’s method createUser), then retrieve it; this time, from the same database, using the getUser method.

Once the User object has been recovered, you can test using the method assertTrue   whether it matches the one saved earlier. It's as simple as that! 😁

Now let’s run the test! Congratulations, you’ve just created your first test on a database!

Your first test!
Your first test!

For now, you’ve only tested the adding and retrieval of data from the table user, but not the table item. But because I’m so nice, I’ll give you the final tests, so you can test all of the CRUD methods of the item table.

Excerpt from ItemDaoTest
class ItemDaoTest {
  ...
   private val NEW_ITEM_PLACE_TO_VISIT = Item("Visit this dream place !", 0, USER_ID)
   private val NEW_ITEM_IDEA = Item("We could do dog sledding ?", 1, USER_ID)
   private val NEW_ITEM_RESTAURANTS = Item("This restaurant looks nice", 2, USER_ID)
  ...
   @Test
   @Throws(InterruptedException::class)
   fun getItemsWhenNoItemInserted() {
       // TEST
       val items = LiveDataTestUtil.getValue(this.database!!.itemDao().getItems(USER_ID))
       assertTrue(items.isEmpty())
   }
 
   @Test
   @Throws(InterruptedException::class)
   fun insertAndGetItems() {
       // BEFORE : Adding demo user & demo items
 
       this.database.userDao().createUser(USER_DEMO)
       this.database.itemDao().insertItem(NEW_ITEM_PLACE_TO_VISIT)
       this.database.itemDao().insertItem(NEW_ITEM_IDEA)
       this.database.itemDao().insertItem(NEW_ITEM_RESTAURANTS)
 
       // TEST
       val items = LiveDataTestUtil.getValue(this.database.itemDao().getItems(USER_ID))
       assertTrue(items.size == 3)
   }
 
   @Test
   @Throws(InterruptedException::class)
   fun insertAndUpdateItem() {
       // BEFORE : Adding demo user & demo items. Next, update item added & re-save it
       this.database.userDao().createUser(USER_DEMO)
       this.database.itemDao().insertItem(NEW_ITEM_PLACE_TO_VISIT)
       val itemAdded = LiveDataTestUtil.getValue(this.database.itemDao().getItems(USER_ID))[0]
       itemAdded.selected = true
       this.database.itemDao().updateItem(itemAdded)
 
       //TEST
       val items = LiveDataTestUtil.getValue(this.database.itemDao().getItems(USER_ID))
       assertTrue(items.size == 1 && items[0].selected)
   }
 
   @Test
   @Throws(InterruptedException::class)
   fun insertAndDeleteItem() {
       // BEFORE : Adding demo user & demo item. Next, get the item added & delete it.
       this.database.userDao().createUser(USER_DEMO)
       this.database.itemDao().insertItem(NEW_ITEM_PLACE_TO_VISIT)
       val itemAdded = LiveDataTestUtil.getValue(this.database.itemDao().getItems(USER_ID))[0]
       this.database.itemDao().deleteItem(itemAdded.id)
 
       //TEST
       val items = LiveDataTestUtil.getValue(this.database.itemDao().getItems(USER_ID))
       assertTrue(items.isEmpty())
   }
 
}

As you can see, we're using all the same methods from our ItemDao interface, and we’re checking to see if they return the information that they should. 

Let's recap!

  • A rule allows you to define the manner in which your tests will be executed.

  • Room provides you with a builder called inMemoryDatabaseBuilder, which creates an instance of your database directly in memory.

Beginning in the next part of this course, we will see how to integrate the calls to our database directly from our TodoListActivity controller in as clean a manner as possible, using architecture components. 😉

Exemple de certificat de réussite
Exemple de certificat de réussite