使用 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 出现问题并且此测试失败,您将无能为力。

我认为尝试编写集成测试比它的价值更麻烦,而且可能不会产生非常有用的测试。