Offscreen Testing — Testing Model
It is always a good idea to test the model layer. This is the layer that provides the data to fill the view that user sees. Whatever your underlying data provider is, it should be viable. Developers mainly focus on testing the business logic which might reside in the Presenter or ViewModel layer of the app along with testing the UI. Model layer is mostly taken for granted to work right.
This article is a part of this post. If you are here just to know how to build tests for model or domain layer, below is summary of the model layer of demo app described in previous article.
The data in our demo app is maintained in local database using Room Persistence Library.
Testing Room Persistent Library
Presuming your familiarity with Room, below is the Room architecture in the demo app.
The entity
@Entity(tableName = "members")
data class Member (
val name : String,
val age : String,
val gender : String,
val email: String,
val phoneNo : String,
val group :String?
){
@PrimaryKey(autoGenerate = true ) var id : Int = 0
}The Dao
@Dao
interface MemberDao {@Insert
fun addMemberDetails(member:Member)@Query("Select name from members where `group` like :groupName")
fun retrieveMembers(groupName : String?):LiveData<List<String>>@Query("Select * from members where name = :name")
fun retrieveDetails(name: String):LiveData<Member>@Query("Update members Set age = :age, gender = :gender, email =:email, phoneNo =:phone where name = :name")
fun updateDetails(age : String, gender : String ,email : String,phone:String, name : String)@Query("Delete from members where name = :name")
fun deleteMember(name : String)
}
Dao is the interface which responsible for CRUD operations. Since the onus of all database functionalities lie with Dao, it is rational to infer the sanity of model layer if Dao class is approved. So, lets build test cases for Dao.
Get Swati Kaushik’s stories in your inbox
Join Medium for free to get updates from this writer.
Testing Dao
First thought that comes to mind while testing DB operations if our tests mess up our DB. Clearly, to test insert/delete/update data operations we need to manipulate the database. This thought doesn’t last for long as Google provides support in Room persistence library itself to set up an in-memory version of database for testing. All testing operations are performed on this database and that’s it! The in-memory database lives only one session of your app. The data doesn’t persist. Consequently, create in-memory DB before testing, perform the operation and close DB after the test.
One important thing to work out — “DB operations/Network calls are not performed on main thread”. It is not only a good coding practice, Android architectural component, Room doesn’t support database access on main thread. However, while executing test operations, we need not run it on background thread. We can use a JUnit test rule — InstantTaskExecutorRule — which replaces the background executor with the one which executes each operation instantly and synchronously.
@RunWith(AndroidJUnit4ClassRunner::class)
class MemberDaoTest {
@get: Rule
val instantTaskExecutorRule : InstantTaskExecutorRule = InstantTaskExecutorRule ()
private lateinit var memberDatabase :MemberDatabase
private lateinit var memberDao :MemberDao
@Before
@Throws (Exception::class)
fun initDatabase(){
memberDatabase = Room.inMemoryDatabaseBuilder(
InstrumentationRegistry.getInstrumentation().context,
MemberDatabase::class.java
).allowMainThreadQueries().build()
memberDao = memberDatabase.memberDao()
}
@After
@Throws (Exception::class)
fun closeDatabase(){
memberDatabase.close()
}The Dao class in the demo app defines some operations which return LiveData objects. It is required to take out the list of members from the LiveData to perform assertions on the correct data type. For this, we define a singleton class
object LiveDataTestUtil {
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 basically monitors the LiveData value throughout the testing session.
Now we can write tests to verify Dao.
@Test
fun testIfEmpty(){
val liveData = memberDatabase.memberDao().retrieveMembers("group")
val items = LiveDataTestUtil.getValue( liveData)
assertTrue(items.isEmpty())
}@Test
fun onInsertingMembersCheckIfRowCountIsCorrect(){
val member = Member("fakeName", "29","male","fakemail@nomail.com","192382481334","fakeGroup")
memberDatabase.memberDao().addMemberDetails(member)
assertEquals(1,LiveDataTestUtil.getValue(memberDatabase.memberDao().retrieveMembers("fakeGroup")).size)
}
@Test
fun onDeletingMembersCheckIfRowCountIsCorrect(){
val member = Member("fakeName", "29","male","fakemail@nomail.com","192382481334","fakeGroup")
memberDatabase.memberDao().addMemberDetails(member)
memberDatabase.memberDao().deleteMember(member.name)
assertEquals(0,LiveDataTestUtil.getValue(memberDatabase.memberDao().retrieveMembers("fakeGroup")).size)}
This way, we can verify the sanity of all backend DB operations performed in the application.
Check out the following articles to other components of an MVP designed app.
Test what you see! — Testing view
Test the middle man! — Testing presenter
Thanks for reading!










