uniflow-kt / uniflow-kt

Uniflow 🦄 - Simple Unidirectional Data Flow for Android & Kotlin, using Kotlin coroutines and open to functional programming

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Allow MockedViewObserver to return state object to improve testing

platramos opened this issue · comments

TLDR:

I would love to write assertions like this:

expectThat(view.state).isEqualTo(SomeState(param1, param2))

but the current implemenation of MockedViewObserver prevents me from doing so.

The Problem

When testing the state of a viewmodel it is sometimes difficult to know which part of the state is incorrect and because MockedViewObserver only exposes hasState and hasEvent Below is an example of the issues I am currently encountering. Given the following test:

    @Before
    fun setup() {
        viewModel = SubmitIDStateViewModel()
        view = viewModel.mockObservers()
    }

    @Test
    fun `should mark state as valid if ID is valid`() {
        private val fakeAddress = Faker.address.build()

        viewModel.updateAddress(fakeAddress)

        coVerify {
            view.hasState(SubmitIDState(address= fakeAddress, id= ID(), isValid= true))
    }

This test fails because isValid never becomes true, but that is very unclear based on the error output:

java.lang.AssertionError: Verification failed: call 1 of 1: Observer(#3).onChanged(eq(SubmitIDState(address=Address(street=21 jump st), id=ID(), isValid=true)))). No matching calls found.

Calls to same method:
1) Observer(#3).onChanged(SubmitIDState(address=Address(street=), id=ID(id=default), isValid=false))
2) Observer(#3).onChanged(SubmitIDState(address=Address(street=21 jump st), id=ID(id=default), isValid=false))
3) Observer(#3).onChanged(SubmitIDState(address=Address(street=21 jump st), id=ID(id=), isValid=false))

One could argue this is a problem with Mockk and the way the verify method handles parameter assertions, but the way MockedViewObserver is implemented feels tightly coupled to the Mockk implementation because it only exposes the hasState method and does not allow for other assertion libraries or approaches to validating internal data on the state object. This is why I would like for the state to be exposed in MockedViewObserver so I could write assertions like this:

    expectThat(view.state).isEqual(SubmitIDState(address= fakeAddress, id= ID() , isValid = true))

I looked a bit deeper into the Uniflow source code and was able to find a way to achieve my desired testing approach.

private lateinit var view: TestViewObserver

view = viewModel.createTestObserver()

assertThat(view.lastStateOrNull)
    .isEqualToComparingFieldByFieldRecursively(
        SubmitIDState(fakeAddress, ID(), true)
     )

This approach wasn't immediately clear based on the docs, I can update them to surface the benefits of TestViewObserver lastStateOrNull and lastStateOrNull if that's helpful.

This commit in open API to let your create the mock the way you want: 06a9e75

viewModel.mockObservers(stateObserverMock..., eventObserverMock)

Please reopen an issue if needed 👍