RxSwift,使用 RxBlocking 的测试不会结束

RxSwift, tests with RxBlocking do not end

我正在尝试测试一个非常简单的视图模型:

struct SearchViewModelImpl: SearchViewModel {
    let query = PublishSubject<String>()
    let results: Observable<BookResult<[Book]>>

    init(searchService: SearchService) {
        results = query
            .distinctUntilChanged()
            .throttle(0.5, scheduler: MainScheduler.instance)
            .filter({ ![=10=].isEmpty })
            .flatMapLatest({ searchService.search(query: [=10=]) })
    }
}

我正在尝试测试从服务中接收错误,所以我将其加倍:

class SearchServiceStub: SearchService {
    let erroring: Bool

    init(erroring: Bool) {
        self.erroring = erroring
    }

    func search(query: String) -> Observable<BookResult<[Book]>> {
        if erroring {
            return .just(BookResult.error(SearchError.downloadError, cached: nil))
        } else {
            return books.map(BookResult.success) // Returns dummy books
        }
    }
}

我正在测试一个以这种方式出错的查询:

func test_when_searchBooksErrored_then_nextEventWithError() {
    let sut = SearchViewModelImpl(searchService: SearchServiceStub(erroring: true))
    let observer = scheduler.createObserver(BookResult<[Book]>.self)

    scheduler
        .createHotObservable([
            Recorded.next(200, ("Rx")),
            Recorded.next(800, ("RxSwift"))
        ])
        .bind(to: sut.query)
        .disposed(by: disposeBag)

    sut.results
        .subscribe(observer)
        .disposed(by: disposeBag)

    scheduler.start()

    XCTAssertEqual(observer.events.count, 2)
}

首先,我只是断言事件计数是否正确,但我只收到一个,而不是两个。我认为这是一个异步的问题所以我将测试更改为使用 RxBlocking:

func test_when_searchBooksErrored_then_nextEventWithError() {
    let sut = SearchViewModelImpl(searchService: SearchServiceStub(erroring: true))
    let observer = scheduler.createObserver(BookResult<[Book]>.self)

    scheduler
        .createHotObservable([
            Recorded.next(200, ("Rx")),
            Recorded.next(800, ("RxSwift"))
        ])
        .bind(to: sut.query)
        .disposed(by: disposeBag)

    sut.results.debug()
        .subscribe(observer)
        .disposed(by: disposeBag)

    let events = try! sut.results.take(2).toBlocking().toArray()

    scheduler.start()

    XCTAssertEqual(events.count, 2)
}

但这永远不会结束。

我不知道我的存根是否有问题,或者 viewmodel 是否有问题,但生产应用程序正常工作,在查询触发时发出事件。

RxTest 和 RxBlocking 的文档非常非常短,带有字符串或整数的经典示例,但与这种流程无关...非常令人沮丧。

您正在使用 MainScheduler.instance 调度程序限制您的查询。尝试删除它,看看会发生什么。这可能就是为什么你只得到一个。您需要在测试时将测试调度程序注入该节流阀。

有几种不同的方法可以让正确的调度程序进入您的模型。根据您当前的代码,依赖注入可以正常工作。

struct SearchViewModelImpl: SearchViewModel {
    let query = PublishSubject<String>()
    let results: Observable<BookResult<[Book]>>

    init(searchService: SearchService, scheduler: SchedulerType = MainScheduler.instance) {
        results = query
            .distinctUntilChanged()
            .throttle(0.5, scheduler: scheduler)
            .filter({ ![=10=].isEmpty })
            .flatMapLatest({ searchService.search(query: [=10=]) })
    }
}

那么在你的测试中:

let sut = SearchViewModelImpl(searchService: SearchServiceStub(erroring: true), scheduler: testScheduler)

此外,您可以将结果事件绑定到 testable Observer

而不是使用 toBocking()
func test_when_searchBooksErrored_then_nextEventWithError() {
    let sut = SearchViewModelImpl(searchService: SearchServiceStub(erroring: true), scheduler: testScheduler)
    let observer = scheduler.createObserver(BookResult<[Book]>.self)

    scheduler
        .createHotObservable([
            Recorded.next(200, ("Rx")),
            Recorded.next(800, ("RxSwift"))
        ])
        .bind(to: sut.query)
        .disposed(by: disposeBag)

    sut.results.bind(to: observer)
     .disposed(by: disposeBag)

    scheduler.start()

    XCTAssertEqual(observer.events.count, 2)
}

虽然 toBlocking() 在某些情况下很有用,但是当您将事件绑定到 testableObserver 时,您会获得更多信息。