I'm trying to write a unit test for a view model using live data.
LoginViewModel.kt
class LoginViewModel @Inject constructor(
val context: Context
): ViewModel() {
val username = MutableLiveData<String>()
val password = MutableLiveData<String>()
val isLoginButtonEnabled = MediatorLiveData<Boolean>().apply {
fun combineLatest(): Boolean {
return !(username.value.isNullOrEmpty() || password.value.isNullOrEmpty())
}
addSource(username) { this.value = combineLatest() }
addSource(password) { this.value = combineLatest() }
}
init {
username.postValue("test")
password.postValue("test")
}
}
LoginViewModelTest.kt
@RunWith(MockitoJUnitRunner::class)
class LoginViewModelTest {
@Rule
@JvmField
val instantTaskExecutorRole = InstantTaskExecutorRule()
private val context = mock(Context::class.java)
private val loginViewModel = LoginViewModel(context)
@Test
fun loginButtonDisabledOnEmptyUsername() {
val observer = mock<Observer<Boolean>>()
loginViewModel.isLoginButtonEnabled.observeForever(observer)
loginViewModel.username.postValue("")
verify(observer).onChanged(false)
}
}
My unit test throws the following exception at the line username.postValue("test")
:
java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked. See http://g.co/androidstudio/not-mocked for details.
The InstantTaskExecutorRule
should provide an execution context when using live data, however it doesn't work when initializing live data in the init
-block. When omitting the init
-block it works as desired, but i need the possibility to initialize live data variables.
Is there any way to make the live data initialization work when unit testing view models?
Live data is lifecycle-aware. postValue() is not lifecycle aware. The value will be updated whenever the main thread runs. In the case of setValue() the said value is called twice, and the value is updated twice, and the observers are notified about the updated data twice.
First, the LiveData should be observed in order to work properly. You can find a utility class at the end of the section to observe your LiveData from test class. Secondly we should add InstantExecutorRule test rule. InstantExecutorRule is a JUnit rule and it comes with androidx.
LiveData is a wrapper that can be used with any data, including objects that implement Collections , such as List . A LiveData object is usually stored within a ViewModel object and is accessed via a getter method, as demonstrated in the following example: Kotlin Java.
I managed to unit test my ViewModel
that was using LiveData
using mentioned rula - InstantTaskExecutorRule
. But in my case the rule val declaration was a bit different:
@Suppress("unused")
@get:Rule
val instantTaskExecutorRule: InstantTaskExecutorRule = InstantTaskExecutorRule()
Edit:
@Before
@Throws(Exception::class)
fun prepare() {
MockitoAnnotations.initMocks(this)
}
Edit2:
For some weird reason I cannot reproduce this :) Also, I think that the problem could be because of the way you're initializing your ViewModel -
private val loginViewModel = LoginViewModel(context)
I assume that it initializes too early, thus it's init block gets called too early too. Maybe it's reasonable to create it in the @Before
method ? Like:
private lateinit var viewModel: LoginViewModel
@Before
@Throws(Exception::class)
fun prepare() {
loginViewModel = LoginViewModel(context)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With