Reputation: 16730
To give some background to this question, I have a ViewModel that waits for some data, posts it to a MutableLiveData, and then exposes all the values through some properties. Here's a short gist of what that looks like:
class QuestionViewModel {
private val state = MutableLiveData<QuestionState>()
private val currentQuestion: Question?
get() = (state.value as? QuestionState.Loaded)?.question
val questionTitle: String
get() = currentQuestion?.title.orEmpty()
...
}
Then, in my test, I mock the data and just run an assertEquals check:
assertEquals("TestTitle", viewModel.questionTitle)
All of this works fine so far, but I actually want my fragment to observe for when the current question changes. So, I tried changing it around to use Transformations.map
:
class QuestionViewModel {
private val state = MutableLiveData<QuestionState>()
private val currentQuestion: LiveData<Question> = Transformations.map(state) {
(it as? QuestionState.Loaded)?.question
}
val questionTitle: String
get() = currentQuestion.value?.title.orEmpty()
...
}
Suddenly, all of my assertions in the test class have failed. I made currentQuestion
public and verified that it's value is null in my unit test. I've determined this is the issue because:
state
LiveDataI have already added the InstantTaskExecutorRule
to my unit test, but maybe that doesn't handle the Transformations
methods?
Upvotes: 2
Views: 1251
Reputation: 9285
Luciano is correct, it's because the LiveData is not being observed. Here is a Kotlin utility class to help with this.
class LiveDataObserver<T>(private val liveData: LiveData<T>): Closeable {
private val observer: Observer<T> = mock()
init {
liveData.observeForever(observer)
}
override fun close() {
liveData.removeObserver(observer)
}
}
// to use:
LiveDataObserver(unit.someLiveData).use {
assertFalse(unit.someLiveData.value!!)
}
Upvotes: 1
Reputation: 1112
I recently had the same problem, I've solved it by adding a mocked observer to the LiveData:
@Mock
private lateinit var observer: Observer<Question>
init {
initMocks(this)
}
fun `test using mocked observer`() {
viewModel.currentQuestion.observeForever(observer)
// ***************** Access currentQuestion.value here *****************
viewModel.questionTitle.removeObserver(observer)
}
fun `test using empty observer`() {
viewModel.currentQuestion.observeForever {}
// ***************** Access currentQuestion.value here *****************
}
Not sure how it works exactly or the consequences of not removing the empty observer the after test.
Also, make sure to import the right Observer class. If you're using AndroidX:
import androidx.lifecycle.Observer
Upvotes: 4
Reputation: 2860
Looks like you're missing the .value
on the it variable.
private val currentQuestion: LiveData<Question> = Transformations.map(state) {
(it.value as? QuestionState.Loaded)?.question
}
Upvotes: 0