AdamMc331
AdamMc331

Reputation: 16730

Unable to access value from `Transformations.map` in unit test

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:

I have already added the InstantTaskExecutorRule to my unit test, but maybe that doesn't handle the Transformations methods?

Upvotes: 2

Views: 1251

Answers (3)

Brian Stewart
Brian Stewart

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

Luciano Ferruzzi
Luciano Ferruzzi

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

cmaynard
cmaynard

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

Related Questions