Search code examples
swiftrx-swiftrxtest

RxTest - How to compare Event<Void> as Void is not Equatable


A ViewModel has an input (an observer) which is bound to tap event of UIButton in UIViewController. This observer is of type AnyObserver<Void>.

In my unit test, this is what I'm expecting:

let correctValues: [Recorded<Event<Void>>] = Recorded.events(
       .next(0, ()),
       .completed(0)
   )

My test observer definition is:

private var voidEventsObserver: TestableObserver<Void>!

let scheduler = TestScheduler(initialClock: 0)
voidEventsObserver = scheduler.createObserver(Void.self)

Assert statement:

XCTAssertEqual(voidEventsObserver.events, correctValues)

I'm getting following error:

Expression type '()' is ambiguous without more context

In Rx, Void events are normal and to properly test ViewModel, one needs to compare them. e.g. .next(0, ()), .completed(0) etc. Void is not Equatable and it doesn't make sense to make it Equatable. However, I need to assert if the event is .next or .error or .completed. How do I assert that part?


Solution

  • Working with Void can be a pain at times.

    Played around with your example, but adding some conditional conformance to Equatable for Result or Event that contain Void is not possible due to Void not being a nominal type or due to these types already having a conflicting conformance to Equatable.

    One approach would be to do something like this:

    XCTAssertEqual(voidEventsObserver.events.count, correctValues.count)
    
    for (actual, expected) in zip(voidEventsObserver.events, correctValues) {
        XCTAssertEqual(actual.time, expected.time, "different times")
    
        let equal: Bool
        switch (actual.value, expected.value) {
        case (.next, .next),
             (.completed, .completed):
            equal = true
        default:
            equal = false
        }
        XCTAssertTrue(equal, "different event")
    }
    

    Now that's ugly as hell and hard to read. The other approach is to introduce a wrapper:

    struct VoidRecord: Equatable {
        let record: Recorded<Event<Void>>
    
        static func == (lhs: Self, rhs: Self) -> Bool {
            guard lhs.record.time == rhs.record.time else { return false }
    
            switch (lhs.record.value, rhs.record.value) {
            case (.next, .next),
                 (.completed, .completed):
                return true
            default:
                return false
            }
        }
    }
    
    XCTAssertEqual(
        voidEventsObserver.events.map(VoidRecord.init),
        correctValues.map(VoidRecord.init)
    )
    

    That reads a lot nicer. Note that the above treats .error events as always being different. If you need to compare error events, simply add this logic from RxSwift to the == function above.