-
Notifications
You must be signed in to change notification settings - Fork 36
9. Unit tests
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:
- Set an initial State
- Send an Action
- 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)
}
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)