ViewModel有一个输入(观察者(,该输入(观察者(绑定到UIViewController
中UIButton
tap
事件。此观察器的类型为AnyObserver<Void>
。
在我的单元测试中,这就是我所期望的:
let correctValues: [Recorded<Event<Void>>] = Recorded.events(
.next(0, ()),
.completed(0)
)
我的测试观察者定义是:
private var voidEventsObserver: TestableObserver<Void>!
let scheduler = TestScheduler(initialClock: 0)
voidEventsObserver = scheduler.createObserver(Void.self)
断言语句:
XCTAssertEqual(voidEventsObserver.events, correctValues)
我收到以下错误:
表达式类型"(("不明确,没有更多上下文
在 Rx 中,Void
事件是正常的,要正确测试 ViewModel,需要比较它们。.next(0, ())
、.completed(0)
等Void
不是Equatable
,让它Equatable
是没有意义的。但是,我需要断言事件是.next
还是.error
或.completed
。我如何断言该部分?
与Void
一起工作有时会很痛苦。
尝试了您的示例,但由于Void
不是名义类型或由于这些类型已经具有与Equatable
冲突的一致性,因此无法为包含Void
的Result
或Event
添加一些条件Equatable
一致性。
一种方法是执行以下操作:
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")
}
现在这很丑陋,很难阅读。另一种方法是引入包装器:
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)
)
这读起来好多了。请注意,上述内容将.error
事件视为始终不同。如果您需要比较错误事件,只需将此逻辑从 RxSwift 添加到上面的==
函数中即可。
这是对我有用的解决方案。它将 Void 替换为事件等同的 Voidvalue。后来它使用 XCTAssertEqual,它是 RxSwift 库的一部分,因此行为和输出将是一致的。
func XCTAssertEqual(_ lhs: [Recorded<Event<Void>>], _ rhs: [Recorded<Event<Void>>], file: StaticString = #file, line: UInt = #line) {
struct VoidValue: Equatable {
static func ==(lhs: Self, rhs: Self) -> Bool {
return true
}
}
let toVoidValueEvent: (Event<Void>) -> Event<VoidValue> = { event in
switch event {
case .completed:
return .completed
case .error(let e1)
:
return .error(e1)
case .next:
return .next(.init())
}
}
let lhsRecords: [Recorded<Event<VoidValue>>] = lhs.map({ Recorded(time: $0.time, value: toVoidValueEvent($0.value)) })
let rhsRecords: [Recorded<Event<VoidValue>>] = rhs.map({ Recorded(time: $0.time, value: toVoidValueEvent($0.value)) })
XCTAssertEqual(lhsRecords, rhsRecords)
}