9. Unit tests

James Shvarts edited this page Dec 11, 2018 · 1 revision

ViewModels and Use Cases contain your business logic. It is essential to unit-test them thoroughly.

In the case of MVI, the decision what to test is a simple one:

  1. Set an initial State
  2. Send an Action
  3. Assert correct States are observed

ViewModels by design are decoupled from your View which means the tests run fast on the JVM. ViewModels have a great testability support. With RxJava, we get powerful testing APIs such as TestObserver and TestScheduler.

The sample app bundled contains a few ViewModel tests. Check them out. Hopefully, you will see that the tests are meaningful and easy to reason about.

Here is are a couple of examples (first testing a happy case--success and the second testing an error condition).

    @Test
    fun `Given notes successfully loaded, when action LoadNotes is received, then State contains notes`() {
        // GIVEN
        val noteList = listOf(Note(1L, "dummy text"))
        val successState = State(noteList)

        whenever(noteListUseCase.loadAll()).thenReturn(Single.just(noteList))

        // WHEN
        testSubject.dispatch(Action.LoadNotes)
        testSchedulerRule.triggerActions()

        // THEN
        inOrder(observer) {
            verify(observer).onChanged(loadingState)
            verify(observer).onChanged(successState)
        }
        verifyNoMoreInteractions(observer)
    }
    @Test
    fun `Given notes failed to load, when action LoadNotes is received, then State contains error`() {
        // GIVEN
        whenever(noteListUseCase.loadAll()).thenReturn(Single.error(RuntimeException()))
        val errorState = State(isError = true)

        // WHEN
        testSubject.dispatch(Action.LoadNotes)
        testSchedulerRule.triggerActions()

        // THEN
        inOrder(observer) {
            verify(observer).onChanged(loadingState)
            verify(observer).onChanged(errorState)
        }
        verifyNoMoreInteractions(observer)
    }

Logging when writing tests

It is very helpful to be able to log Actions and States emitted while writing unit tests. For States to be logged to console, you may want to add a println() in doOnNext() when emitting States in the ViewModel being tested:

        disposables += loadNotesChange
            .scan(initialState, reducer)
            .filter { !it.isIdle }
            .distinctUntilChanged()
            .doOnNext { println("Received state: $it") }
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(state::setValue, Timber::e)
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.