1

Consider the following code:

sealed interface State {
    object Loading : State
    data class Content(val someString: String) : State
}

class SomeViewModel(getSomeStringUseCase: GetSomeStringUseCase) : ViewModel() {

    private val _someString = MutableLiveData<State>()
    val someString: LiveData<State> = _someString

    init {
        _someString.value = State.Loading
        viewModelScope.launch {
            _someString.value = State.Content(getSomeStringUseCase())
        }
    }
}

In a unit test it's pretty simple to test and assert the last value emitted by someString, however, if I want to assert all values emitted it gets more complicated because I can't subscribe to someString before SomeViewModel is initialized and if I do the subscription right after the initialization it is too late and the values were already emitted:

class SomeViewModelTest {

    @MockK
    private lateinit var getSomeStringUseCase: GetSomeStringUseCase
    private lateinit var viewModel: SomeViewModel

    @Before
    fun setUp() {
        coEvery { getSomeStringUseCase() } returns "Some String!"
        viewModel = SomeViewModel(getSomeStringUseCase)
    }

    // This test fails
    @Test
    fun test() {
        val observer = mockk<Observer<State>>(relaxed = true)
        viewModel.someString.observeForever(observer)
        verifyOrder {
            observer.onChanged(State.Loading)
            observer.onChanged(State.Content("Some String!"))
        }
    }
}
dsantamaria
  • 188
  • 1
  • 8
  • You could throw a ``delay`` into the start of your coroutine to give the observer a chance to connect and see that initial state. That's what you're testing really, right? If a delay in that initialisation results in observers seeing the correct state until the initialisation completes – cactustictacs Jan 04 '22 at 20:19
  • That would cause unnecessary extra loading time – dsantamaria Jan 04 '22 at 20:57
  • Oh sorry, I meant in the call it's making, in the dependency you're passing in. ``getSomeStringUseCase()`` needs to block long enough for the observer to be set up and receive that first value – cactustictacs Jan 04 '22 at 22:44
  • It won't cause unnecessary extra loading time if you use a testing coroutine dispatcher that automatically skips the delay. This is simpler since `kotlinx-coroutines` 1.6.0, which introduced [new testing APIs](https://github.com/Kotlin/kotlinx.coroutines/tree/master/kotlinx-coroutines-test). – Alex Krupa Jan 05 '22 at 08:45

0 Answers0