Test-Driven Development in Android MVVM Architecture (Part 2-Setup & Unit testing for LiveData)

Shehzab Ahammad
5 min readMay 25, 2020

--

Image by Photo Mix from Pixabay

So here we go…!!!

Before begin, if you didn’t check with my previous article, I recommend you check it here.

We have a small sample android application that hosts a Login screen and Home screen. You can clone it here. I recommend you to clone this repo and checkout to tdd-starter-app branch, so you can easily understand the flow ✌.

git clone https://github.com/shehzab.developer/Test_Driven_Development.git

git checkout -b tdd-starter-app

So, now you had cloned this repo and checkout to tdd-starter-app.

Let's start to swim (I mean to code😊 )

We will start step by step. Lets first start with a sample test method.
The test you write will check that when you call the validateUser , the Boolean value will get fired. Go and check your LoginViewModel class for this method.

So let's start!!!

Step 1:Open your project, goto package login, open LoginViewModel class.

Step 2: Right-click LoginViewModel and select Generate > test

Right-click LoginViewModel and select Generate
Select Test…

Step 3: Pop-up appears. Select default values (cross-check with the image below) and click Ok.

Step 4: Pop-up appears. Select a test and click Ok.

Notice the generated LoginViewModelTest class in test/login/ package.

Step 5: Now in your LoginViewModelTest class write a new test called validateUser_Boolean .

LoginViewModelTest.kt

class LoginViewModelTest {

@Test
fun validateUser_Boolean() {
}
}

But wait for the second... What about the application context???
So here it comes AndroidX Test libraries which include classes and methods that provide you with versions of components like Applications and Activities that are meant for tests. So, just follow the below steps to set up AndroidX Test. Add below dependencies into your app module’s build.gradle.

1. Add the AndroidX core and ext dependencies.
testImplementation 'androidx.test.ext:junit-ktx:1.1.1'
testImplementation 'androidx.test:core-ktx:1.2.0'
2. Add the Robolectric testing library dependencies.
testImplementation 'org.robolectric:robolectric:4.3.1'
3. Annotate the class with the AndroidJUnit4 test runner.
@RunWith(AndroidJUnit4::class)
4. Write the AndroidX test code.
val loginViewModel= LoginViewModel(ApplicationProvider.getApplicationContext())

So now we got an application context.

Now call validateUser on loginViewModel.
val user = User("android","123456")
loginViewModel.validateUser(user)

So code look like:

@RunWith(AndroidJUnit4::class)
class LoginViewModelTest {

@Test
fun validateUser_Boolean() {
val loginViewModel = LoginViewModel(ApplicationProvider.getApplicationContext())
val user = User("android","123456")
loginViewModel.validateUser(user)
}
}

We can take out application context call to @Before method and make an assert call to check the test.

@RunWith(AndroidJUnit4::class)
class LoginViewModelTest {
private lateinit var loginViewModel: LoginViewModel

@Before
fun setup() {
loginViewModel = LoginViewModel(ApplicationProvider.getApplicationContext())
}

@Test
fun validateUser_Boolean() {

val user = User("android", "123456")
assertTrue(loginViewModel.validateUser(user))
}
}

Run the test. Cool... the test passed.

So it’s just started. Our aim is to test LiveData value. So just follow the same things and call doLogin from LoginViewModel where the return type is LiveData value.

Step 6: Now in your LoginViewModelTest class write a new test called doLogin_LiveData .

@Test
fun doLogin_LiveData() {

val user = User("android", "123456")
loginViewModel.doLogin(user)

//TODO test LiveData
}

To test LiveData, we need two things.
1. Use InstantTaskExecutorRule.
testImplementation 'androidx.arch.core:core-testing:2.1.0'
2. Ensure LiveData observation.

InstantTaskExecutorRule is a JUnit Rule. We can use it with @get:Rule annotation, it causes some code in the InstantTaskExecutorRule class to be run before and after the test.

When we use LiveData , we commonly have activity or fragment to observe the LiveData. This makes a problem in our LoginViewModelTest class because we don't have any activity or fragments to observe it. To get around this, we need to use observeForever method, which ensures the LiveData is constantly observers, without needing a LifecycleOwner.

To get over this, create a Kotlin file name as LiveDataTestUtil.kt and copy-paste the below code to this file.

import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException

/**
* Gets the value of a
[LiveData] or waits for it to have one, with a timeout.
*
* Use this extension from host-side (JVM) tests. It's recommended to use it alongside
* `InstantTaskExecutorRule` or a similar mechanism to execute tasks synchronously.
*/
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun <T> LiveData<T>.getOrAwaitValue(
time: Long = 2,
timeUnit: TimeUnit = TimeUnit.SECONDS,
afterObserve: () -> Unit = {}
): T {
var data: T? = null
val
latch = CountDownLatch(1)
val observer = object : Observer<T> {
override fun onChanged(o: T?) {
data = o
latch.countDown()
this@getOrAwaitValue.removeObserver(this)
}
}
this.observeForever(observer)

try {
afterObserve.invoke()

// Don't wait indefinitely if the LiveData is not set.
if (!latch.await(time, timeUnit)) {
throw TimeoutException("LiveData value was never set.")
}

} finally {
this.removeObserver(observer)
}

@Suppress("UNCHECKED_CAST")
return data as T
}

So, the above code will take care of LiveData observer and we don't need to put our hands on it.

So now our code looks like

                        ....    @get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()

....
....
....

@Test
fun doLogin_LiveData() {

val user = User("android", "123456")
loginViewModel.doLogin(user)
assertTrue(loginViewModel.response.getOrAwaitValue())
}
}

Cool… the test passed!!!

For complete source code for this chapter, checkout repo to tdd-unit-testing.

git checkout -b tdd-unit-testing

What’s Next?

Next, we are going to dive into the UI stuff. Check it out Part-3 by clicking here or in the below link.

Thank you for your time to read this article. If you like it please give me your clap 👏 , so others can find it too.

--

--