使用 TestScheduler 订阅序列时未获得所有预期事件
Not getting all expected events when subscribing to sequence with TestScheduler
我正在尝试在使用 ReactorKit 和 Realm/RxRealm 构建的应用程序中为 Reactor
编写集成测试。
我在使用 TestScheduler
模拟用户操作和测试预期发射状态时遇到问题。
简而言之,我的问题是这样的:我正在绑定一个操作,使我的 Reactor 将一个项目保存到 Realm,我的 Reactor 也在 Realm 中观察到这个对象的变化,我希望我的 Reactor 发出从 Realm 观察到该项目的新状态。
我看到的是我的测试没有及时发出新保存的对象来断言它的值,它是在我的测试断言运行后发出的。
涉及相当多的代码,但试图将其缩减为一个独立的示例,大致如下所示:
struct MyObject {
var counter: Int = 0
}
class MyReactor: Reactor {
enum Action {
case load
case mutateState
}
enum Mutation {
case setObject(MyObject)
}
struct State {
var object: MyObject?
}
func mutate(action: Action) -> Observable<Mutation> {
switch action {
case .load:
return service.monitorObject().map(Mutation.setObject)
case .mutateState:
guard var myObject = currentState.object else { return .empty() }
myObject.counter += 1
return service.save(myObject).andThen(.empty())
}
}
func reduce(state: State, mutation: Mutation) -> Observable<State> {
var newState = state
switch mutation {
case let .setObject(object):
// Should be called twice in tests, once on load, once after mutateState action
newState.object = object
}
}
}
struct Service {
// There is always at least one default instance of `MyObject` in Realm.
func monitorObject() -> Observable<MyObject> {
return Observable
.collection(from: realm.objects(MyObject.self))
.map { [=10=].first! }
}
func save(_ object: MyObject) -> Completable {
return Completable.create { emitter in
try! realm.write {
realm.add(object, update: .modified)
}
emitter(.completed)
return Disposables.create()
}
}
}
class MyTest: QuickSpec {
var scheduler: TestScheduler!
var sut: MyReactor!
var disposeBag: DisposeBag!
var service: Service!
var config: Realm.Configuration!
override func spec() {
beforeEach {
config = Realm.Configuration(inMemoryIdentifier: UUID().uuidString)
scheduler = TestScheduler(initialClock: 0)
disposeBag = DisposeBag()
sut = MyReactor()
service = Service(realmConfig: config)
}
describe("when my reactor gets a mutateState action") {
it("should mutate state") {
scheduler.createHotObservable([
.next(1, Action.load),
.next(2, Action.mutateState),
])
.bind(to: sut.action)
.disposed(by: disposeBag)
let response = scheduler.start(created: 0, subscribed: 0, disposed: 1000) {
sut.state.map(\.object)
}
// Counter always equals 0
XCTAssertTrue(response.events.last!.value.element!!.counter == 1)
}
}
}
}
我期望发生的是在 XCTAssertTrue
被击中之前,我的 Reactor 的状态被设置为第二次。实际发生的是断言被初始加载状态击中,然后,我的反应器的状态被再次设置。
我认为我的问题可能与调度程序有关。我尝试的是将测试调度程序注入我的服务并在我的 monitorObject
函数上执行 observeOn(testScheduler)
。但我仍然观察到在第二次设置反应器状态之前断言被击中。我也不确定 RxRealm/Realm 更改集通知的细微差别是否是原因 - 不确定如何验证是否是这种情况。
希望问题和疑问是清楚的。在此先感谢您的帮助。
所以您正在尝试测试 Realm 是否有效。我不使用 Realm,但根据您的描述,它可能会在内部线程上更新对象,然后您会在后续循环中获得发射。
您可以使用 XCTestExpectation 对其进行测试。这是 Apple 的文档:https://developer.apple.com/documentation/xctest/asynchronous_tests_and_expectations/testing_asynchronous_operations_with_expectations
但是请注意,如果 Realm 出现问题并且此测试失败,您将无能为力。
我认为尝试编写集成测试比它的价值更麻烦,而且可能不会产生非常有用的测试。
我正在尝试在使用 ReactorKit 和 Realm/RxRealm 构建的应用程序中为 Reactor
编写集成测试。
我在使用 TestScheduler
模拟用户操作和测试预期发射状态时遇到问题。
简而言之,我的问题是这样的:我正在绑定一个操作,使我的 Reactor 将一个项目保存到 Realm,我的 Reactor 也在 Realm 中观察到这个对象的变化,我希望我的 Reactor 发出从 Realm 观察到该项目的新状态。
我看到的是我的测试没有及时发出新保存的对象来断言它的值,它是在我的测试断言运行后发出的。
涉及相当多的代码,但试图将其缩减为一个独立的示例,大致如下所示:
struct MyObject {
var counter: Int = 0
}
class MyReactor: Reactor {
enum Action {
case load
case mutateState
}
enum Mutation {
case setObject(MyObject)
}
struct State {
var object: MyObject?
}
func mutate(action: Action) -> Observable<Mutation> {
switch action {
case .load:
return service.monitorObject().map(Mutation.setObject)
case .mutateState:
guard var myObject = currentState.object else { return .empty() }
myObject.counter += 1
return service.save(myObject).andThen(.empty())
}
}
func reduce(state: State, mutation: Mutation) -> Observable<State> {
var newState = state
switch mutation {
case let .setObject(object):
// Should be called twice in tests, once on load, once after mutateState action
newState.object = object
}
}
}
struct Service {
// There is always at least one default instance of `MyObject` in Realm.
func monitorObject() -> Observable<MyObject> {
return Observable
.collection(from: realm.objects(MyObject.self))
.map { [=10=].first! }
}
func save(_ object: MyObject) -> Completable {
return Completable.create { emitter in
try! realm.write {
realm.add(object, update: .modified)
}
emitter(.completed)
return Disposables.create()
}
}
}
class MyTest: QuickSpec {
var scheduler: TestScheduler!
var sut: MyReactor!
var disposeBag: DisposeBag!
var service: Service!
var config: Realm.Configuration!
override func spec() {
beforeEach {
config = Realm.Configuration(inMemoryIdentifier: UUID().uuidString)
scheduler = TestScheduler(initialClock: 0)
disposeBag = DisposeBag()
sut = MyReactor()
service = Service(realmConfig: config)
}
describe("when my reactor gets a mutateState action") {
it("should mutate state") {
scheduler.createHotObservable([
.next(1, Action.load),
.next(2, Action.mutateState),
])
.bind(to: sut.action)
.disposed(by: disposeBag)
let response = scheduler.start(created: 0, subscribed: 0, disposed: 1000) {
sut.state.map(\.object)
}
// Counter always equals 0
XCTAssertTrue(response.events.last!.value.element!!.counter == 1)
}
}
}
}
我期望发生的是在 XCTAssertTrue
被击中之前,我的 Reactor 的状态被设置为第二次。实际发生的是断言被初始加载状态击中,然后,我的反应器的状态被再次设置。
我认为我的问题可能与调度程序有关。我尝试的是将测试调度程序注入我的服务并在我的 monitorObject
函数上执行 observeOn(testScheduler)
。但我仍然观察到在第二次设置反应器状态之前断言被击中。我也不确定 RxRealm/Realm 更改集通知的细微差别是否是原因 - 不确定如何验证是否是这种情况。
希望问题和疑问是清楚的。在此先感谢您的帮助。
所以您正在尝试测试 Realm 是否有效。我不使用 Realm,但根据您的描述,它可能会在内部线程上更新对象,然后您会在后续循环中获得发射。
您可以使用 XCTestExpectation 对其进行测试。这是 Apple 的文档:https://developer.apple.com/documentation/xctest/asynchronous_tests_and_expectations/testing_asynchronous_operations_with_expectations
但是请注意,如果 Realm 出现问题并且此测试失败,您将无能为力。
我认为尝试编写集成测试比它的价值更麻烦,而且可能不会产生非常有用的测试。