Testing Asynchronicity in Swift & Kotlin
Comparing Combine, ReactiveSwift and RxJava for tests
In this series we introduced a Clean MVVM architecture for writing consistent code in Swift and Kotlin.
Central to our Clean MVVM architecture are reactive streams, which implicitly introduce asynchronicity into our app. Testing reactive streams is not straightforward — when writing tests that use Reactive streams, we must find a way to either wait or block until our streams have completed or data has been emitted.
Move execution to the main thread
On Android, we can override the thread on which reactive streams are invoked, so that normally asynchronous calls are made synchronous. We do this with two beforeEach
methods in an ExtendWith
block.
@ExtendWith(InstantExecutorExtension::class,
RxTestSchedulersExtension::class
The InstantExecutorExtension
applies to LiveData, and is adapted from this blog post. The RxTestSchedulersExtension
applies to thread execution in RxJava. Both implementations can be found below:
Evaluating streams with TestObserver
Reactive streams need tests, and for this, Combine, ReactiveSwift, and RxJava have the concept of a TestObserver. A TestObserver is a class that is given a stream, such as a Publisher (Combine), SignalProducer (ReactiveSwift) or Observable (RxJava), for which it keeps a history of the emitted values. You also may use a TestObserver to wait for a given event to be emitted before a test can proceed.
Note that we typically declare TestObserver objects as a shared object in each inner class/context, we assign them in the beforeEach
blocks, and then we interrogate them in each of the then tests.
Combine
To evaluate streams in Combine, use the CombineExpectations package. This package defines a Recorder
object, which is functionally equivalent to a TestObserver on Android.
let eventsRecorder = context.viewModel.events.record()
context.viewModel.onEditProfileClick()
let event = try eventsRecorder.next().get()
XCTAssertEqual(event, PSSAccountViewModel.Event.triggerProfileOfflineAlert)
ReactiveSwift
ReactiveSwift does not natively ship with a TestObserver, but Kickstarter has helpfully defined such a class in their repository. Here is an example of observing that a .flagProfile
event gets emitted:
RxJava
To evaluate streams in RxJava, we use the TestObserver
class which is defined as part of the io.reactivex.observers
package. Here is an example of observing that a FlagProfile
event get emitted.
LiveData
Our use of LiveData is generally as a substitute for BehaviorSubject at the View layer. As a result, if we have applied the InstantExecutor extension described above, we can generally just access the .value
property directly off of the main thread in a test and do not require any special handling or observers.
More in the series
- Clean MVVM in Swift & Kotlin
- ViewModels in Swift & Kotlin
- Logic Classes in Swift & Kotlin
- Repositories and Domain Models in Swift & Kotlin
- API classes in Swift & Kotlin
- Views in Swift & Kotlin
- Testing MVVM in Swift & Kotlin
- Testing Asynchronicity in Swift & Kotlin ← you are here
- Clean MVVM Summary
Other series you might like
Clean API Architecture (2021)
Classes, execution patterns, and abstractions when building a modern API endpoint.
Android Activity Lifecycle considered harmful (2021)
Android process death, unexplainable NullPointerExceptions, and the MVVM lifecycle you need right now
About the author
Eric Silverberg is the CEO and founder of Perry Street Software, publisher of two of the world’s largest LGBTQ+ dating apps on iOS and Android.