1

我刚刚开始学习 RxSwift 并尝试构建一个示例应用程序来练习这些概念。

我编写了一个 QuestionViewModel,它从 QuestionOps 类加载问题列表。QuestionOps 有一个 getQuestions 函数,它返回 Single<[Question]>。

我面临的问题是,如何在测试 QuestionViewModel 时模拟 QuestionOps 类的行为。

public class QuestionsListViewModel {

    public var questionOps: QuestionOps!

    private let disposeBag = DisposeBag()
    private let items = BehaviorRelay<[QuestionItemViewModel]>(value: [])
    public let loadNextPage = PublishSubject<Void>()
    public var listItems: Driver<[QuestionItemViewModel]>
    public init() {
        listItems = items.asDriver(onErrorJustReturn: [])

        loadNextPage
            .flatMapFirst { self.questionOps.getQuestions() }
            .map { $0.map { QuestionItemViewModel($0) } }
            .bind(to: items)
            .disposed(by: disposeBag)
    }
}
public class QuestionOps {

    public func getQuestions() -> Single<[Question]> {

        return Single.create { event -> Disposable in

            event(.success([]))
            return Disposables.create()
        }
    }
}

我创建了这个 MockQuestionOps 用于测试目的:

public class MockQuestionOps : QuestionOps {

    //MARK: -
    //MARK: Responses
    public var getQuestionsResponse: Single<[Question]>?

    public func getQuestions() -> Single<[Question]> {
        self.getQuestionsResponse = Single.create { event -> Disposable in

            return Disposables.create()
        }
        return self.getQuestionsResponse!
    }
}

在我的测试用例中,我正在执行以下操作:

/// My idea here is to test in following maner:
/// - at some point user initates loading
/// - after some time got network response with status true
func testLoadedDataIsDisplayedCorrectly() {

    scheduler = TestScheduler(initialClock: 0)
    let questionsLoadedObserver = scheduler.createObserver([QuestionItemViewModel].self)

    let qOps = MockQuestionOps()
    vm = QuestionsListViewModel()
    vm.questionOps = qOps
    vm.listItems
        .drive(questionsLoadedObserver)
        .disposed(by: disposebag)

    // User initiates load questions
    scheduler.createColdObservable([.next(2, ())])
        .bind(to: vm.loadNextPage)
        .disposed(by: disposebag)

    // Simulating question ops behaviour of responding
    // to get question request

    /// HERE: -----------    
    /// This is where I am stuck
    /// How should I tell qOps to send particular response with delay

    scheduler.start()

    /// HERE: -----------
    /// How can I test list is initialy empty
    /// and after loading, data is correctly loaded
}
4

1 回答 1

1

这是一个完整的,可编译的答案(不包括导入。)

  • 你告诉 qOps 通过给它一个冷的测试 observable 来发出正确的值。

  • 您可以通过将测试观察者收集的事件与预期结果进行比较来测试输出。

没有“列表最初为空”的概念。该列表始​​终为空。它会随着时间的推移发出值,您要测试的是它是否发出了正确的值。

class rx_sandboxTests: XCTestCase {

    func testLoadedDataIsDisplayedCorrectly() {

        let scheduler = TestScheduler(initialClock: 0)
        let disposebag = DisposeBag()
        let questionsLoadedObserver = scheduler.createObserver([QuestionItemViewModel].self)

        let qOps = MockQuestionOps(scheduler: scheduler)
        let vm = QuestionsListViewModel(questionOps: qOps)
        vm.listItems
            .drive(questionsLoadedObserver)
            .disposed(by: disposebag)

        scheduler.createColdObservable([.next(2, ())])
            .bind(to: vm.loadNextPage)
            .disposed(by: disposebag)

        scheduler.start()

        XCTAssertEqual(questionsLoadedObserver.events, [.next(12, [QuestionItemViewModel(), QuestionItemViewModel()])])
    }
}

protocol QuestionOpsType {
    func getQuestions() -> Single<[Question]>
}

struct MockQuestionOps: QuestionOpsType {
    func getQuestions() -> Single<[Question]> {
        return scheduler.createColdObservable([.next(10, [Question(), Question()]), .completed(10)]).asSingle()
    }
    let scheduler: TestScheduler
}

class QuestionsListViewModel {

    let listItems: Driver<[QuestionItemViewModel]>
    private let _loadNextPage = PublishSubject<Void>()

    var loadNextPage: AnyObserver<Void> {
        return _loadNextPage.asObserver()
    }

    init(questionOps: QuestionOpsType) {
        listItems = _loadNextPage
            .flatMapFirst { [questionOps] in
                questionOps.getQuestions().asObservable()
            }

            .map { $0.map { QuestionItemViewModel($0) } }
            .asDriver(onErrorJustReturn: [])
    }
}

struct Question { }
struct QuestionItemViewModel: Equatable {
    init() { }
    init(_ question: Question) { }
}
于 2020-01-20T03:32:20.563 回答