Observe StateFlow emission in ViewModel initialization

Viewed 1355

I have a view model that takes in an initial ViewState object and has a publicly accessible state variable which can be collected.

class MyViewModel<ViewState>(initialState: ViewState) : ViewModel() {
    val state: StateFlow<ViewState> = MutableStateFlow(initialState)
    val errorFlow: SharedFlow<String> = MutableSharedFlow()

    init {
        performNetworkCall()
    }

    private fun performNetworkCall() = viewModelScope.launch {
        Network.makeCall(
            "/someEndpoint",
            onSuccess = {
                (state as MutableStateFlow).tryEmit(<some new state>)
            },
            onError = {
                (errorFlow as MutableSharedFlow).tryEmit("network failure")
            }
        )
    }
}

When observing this state from a fragment, I can see the initial state (loading for example) and I collect the change when the network call completes successfully (for example, to a loaded state.)

However, I am at a loss as to how to observe this emission from my ViewModelUnitTest. I use kotlin turbine to test emissions for my state and shared flows, but I can only observe emissions that occur after I call viewModel.state.test or viewModel.errorFlow.test.

Since I cannot reference viewModel.state or viewModel.errorFlow prior to initialization of the ViewModel, how can I write a test to validate that my initialization logic performs correctly and emits the expected result based on the mocked behavior of Network.makeCall - whether it be a new emission of state or an errorFlow emission?

1 Answers

Since your network class doesn't have any reference in the view model, it isn't possible to mock/fake Network class' behavior. So, create a reference for Network. To capture what's going on in the init block, you can lazy initialize your view model in the test class. It is described in here as the second way. Roughly, your classes look similar to:

class MyViewModel<ViewState>(
    initialState: ViewState,
    network: Network
) : ViewModel() {
    val state: StateFlow<ViewState> = MutableStateFlow(initialState)
    val errorFlow: SharedFlow<String> = MutableSharedFlow()

    init {
        performNetworkCall()
    }

    private fun performNetworkCall() = viewModelScope.launch {
        network.makeCall(
            "/someEndpoint",
            onSuccess = {
                (state as MutableStateFlow).tryEmit(<some new state>)
            },
            onError = {
                (errorFlow as MutableSharedFlow).tryEmit("network failure")
            }
        )
    }
}

@ExperimentalCoroutinesApi
class MyViewModelTest {

    @get:Rule
    var coroutinesTestRule = CoroutinesTestRule()

    val viewModel by lazy { MyViewModel(/*pass arguments*/) }
}

CoroutineTestRule can be found in here.

Related