DEV Community

loading...

How to Unit Test LiveData and ViewModel

Arth Limchiu
Project-based Android Books @ https://gumroad.com/arthlimchiu | Be humble, keep learning, and share.
Updated on ・2 min read

Originally published on my blog.

Source code

Import dependencies

implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
testImplementation 'androidx.arch.core:core-testing:2.0.1'
testImplementation 'org.mockito:mockito-core:2.28.2'
Enter fullscreen mode Exit fullscreen mode

Check the official Android documentation for the latest version of these Android packages.

For Mockito, check out their Github repository for the latest version.

Create a simple User class

data class User(val id: Int)
Enter fullscreen mode Exit fullscreen mode

Create a ViewModel

class MainViewModel : ViewModel() {

    private val _user = MutableLiveData<User>()

    val user: LiveData<User>
        get() = _user

    fun fetchUser(id: Int) {
        val user = User(id)

        _user.value = user
    }
}
Enter fullscreen mode Exit fullscreen mode

Create a helper function to mock classes with types (generics)

Create a kotlin file - MockitoUtils.kt inside your test folder.

inline fun <reified T> mock(): T = Mockito.mock(T::class.java)
Enter fullscreen mode Exit fullscreen mode

Creating the unit test

class MainViewModelTest {

    @get:Rule
    val rule = InstantTaskExecutorRule()

    private lateinit var viewModel: MainViewModel

    private val observer: Observer<User> = mock()

    @Before
    fun before() {
        viewModel = MainViewModel()
        viewModel.user.observeForever(observer)
    }

    @Test
    fun fetchUser_ShouldReturnUser() {
        val expectedUser = User(1)

        viewModel.fetchUser(expectedUser.id)

        val captor = ArgumentCaptor.forClass(User::class.java)
        captor.run {
            verify(observer, times(1)).onChanged(capture())
            assertEquals(expectedUser, value)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

@get:Rule
val rule = InstantTaskExecutorRule()
Enter fullscreen mode Exit fullscreen mode

What this rule basically does is allow us to run LiveData synchronously. This rule is from the core-testing package that was imported earlier.

private val observer: Observer<User> = mock()
Enter fullscreen mode Exit fullscreen mode

The helper function that we made will help us be able to mock our Observer. Try mocking it the standard way and you'll see what I mean - mock(Observer<User>::class.java).

val captor = ArgumentCaptor.forClass(User::class.java)
captor.run {
    verify(observer, times(1)).onChanged(capture())
    assertEquals(expectedUser, value)
}
Enter fullscreen mode Exit fullscreen mode

ArgumentCaptor does what the class name is called - capture argument(s). We capture() the argument when the onChanged(...) method of our Observer is called.

Adding verify(observer, times(1)).onChanged(capture()) allows you to capture instances where the LiveData is called multiple times when you expect it to be called only once.

Assert that the expectedUser that we've created is equal to the emitted user of our LiveData.

Lastly, run the goddamn test.

Discussion (1)

Collapse
omarbeshary profile image
Omar Beshary

Thank you 🙏