Say you are like me and work with ReSwift for unidirectional data flow goodness and employ RxSwift for your reactive cravings. You want to dispatch a ReSwift.Action when an RxSwift.Observable signal produces a new value. How do you write tests for that wiring?

In code, it could look like this:

someObservable
    .map { SomeAction($0) }
    .subscribe(onNext: store.dispatch($0))
    .addDisposableTo(disposeBag)

Testing that code requires a bit more understanding of both libraries than their respective documentation reveals.

Here’s an example:

import ReSwift

struct AppState {
    var count = 0
}

typealias DefaultStore = Store<AppState>

struct ChangingCount: ReSwift.Action {
    let value: Int
    init(value: Int) {
        self.value = value
    }
}

Imagine a reducer that takes incoming ChangingCount.value and replaces AppState.value with that. Simple.

Now you want to dispatch a ChangingCount action when some signal produces a new value. You need to create an observer for the signal and store it somewhere. So a friendly helper class comes to mind:

import RxSwift

class Dispatcher {
    let store: DefaultStore
    let disposeBag = DisposeBag()
    
    init(store: DefaultStore) {
        self.store = store
    }
    
    func wireSignalToAction(signal: Observable<Int>) {
        signal.map(ChangingCount.init(value:))
            .subscribe(onNext: { [weak self] in
                self?.store.dispatch($0) })
            .addDisposableTo(disposeBag)
    }
}

And that’s it. Now how can you make sure it does what it should do? What do unit tests look like?

Taking a look at RxSwift’s TestObserver and TestScheduler, you can record incoming events yourself and perform checks. To record events, you hijack a ReSwift.Store’s dispatchFunction with a closure that captures the incoming action. Using a TestScheduler, you can time production of events in the signal and keep track of the time the action comes in:

import RxSwift
import RxTest

class DispatchingTests: XCTestCase {
    /// Replacement for your real reducer because you won't need it.
    struct NullReducer: ReSwift.Reducer {
        func handleAction(action: Action, state: AppState?) -> AppState {
            return AppState(value: -1)
        }
    }
    
    func testDispatchesValueChanges() {
        
        // Set up a signal. Subscription per default starts at time=200
        let scheduler = TestScheduler(initialClock: 0)
        let signal = scheduler.createHotObservable([
            next(300, 98),
            next(600, 12)
            ])
        
        // Set up the store and record matching actions.
        let storeDouble = Store(reducer: NullReducer(), state: nil)
        var actions = [Recorded<Event<ChangingCount>>]()
        storeDouble.dispatchFunction = {
            guard let action = $0 as? ChangingCount else { return () }
            // `return f()` where `f() -> Void` is just like `return ()`.
            // We need to return something to satisy the `-> Any`.
            return actions.append(Recorded(time: scheduler.clock, value: .next(action)))
        }

        // Configure the object under test and start emitting
        // values in virtual time.
        let dispatcher = Dispatcher(store: storeDouble)
        dispatcher.wireSignalToAction(signal)
        scheduler.start()

        XCTAssertEqual(actions, [
            next(300, ChangingCount(value: 98)),
            next(600, ChangingCount(value: 12))
            ])
    }
}