【问题标题】:Test PublishSubject for ViewState测试 ViewState 的 PublishSubject
【发布时间】:2019-10-18 20:58:00
【问题描述】:

我正在尝试测试我的 ViewModel 的主要功能。重要的步骤是测试加载状态完成。但可以肯定的是,为了更好的测试,测试所有状态可能会很有趣。

我阅读了很多关于 RxTest 和 RxBlocking 的帖子和信息,但我无法测试这个模块。如果有人可以帮助我,那就太好了!

struct Product: Equatable { }
struct Promotion { }

protocol ProductsRepository {
    func fetchProducts() -> Observable<Products>
    func fetchPromotions()  -> Observable<[Promotion]>
}

struct ProductCellViewModel: Equatable {
    let product: Product
}

struct Products {
    let products: [Product]
}

enum ProductsViewState: Equatable {
    case loading
    case empty
    case error
    case loaded ([ProductCellViewModel])
}

class ProductsViewModel {

    var repository: ProductsRepository

    let disposeBag = DisposeBag()
    private var productCellViewModel: [ProductCellViewModel]
    private var promotions: [Promotion]

    // MARK: Input

    init(repository: ProductsRepository) {
        self.repository = repository
        productCellViewModel = [ProductCellViewModel]()
        promotions = [Promotion]()
    }

    func requestData(scheduler: SchedulerType) {
        state.onNext(.loading)
        resetCalculate()
        repository.fetchProducts()
            .observeOn(scheduler)
            .flatMap({ (products) -> Observable<[ProductCellViewModel]> in
                return self.buildCellViewModels(data: products)
            }).subscribe(onNext: { (cellViewModels) in
                self.productCellViewModel = cellViewModels
            }, onError: { (error) in
                self.state.onNext(.error)
            }, onCompleted: {
                self.repository.fetchPromotions()
                    .flatMap({ (promotions) -> Observable<[Promotion]> in
                        self.promotions = promotions
                        return Observable.just(promotions)
                    }).subscribe(onNext: { (_) in
                        self.state.onNext(.loaded(self.productCellViewModel))
                    }, onError: { (error) in
                        self.state.onNext(.error)
                    }).disposed(by: self.disposeBag)
            }).disposed(by: disposeBag)
    }

    // MARK: Output

    var state = PublishSubject<ProductsViewState>()

    // MARK: ViewModel Map Methods

    private func buildCellViewModels(data: Products) -> Observable <[ProductCellViewModel]> {
        var viewModels = [ProductCellViewModel]()
        for product in data.products {
            viewModels.append(ProductCellViewModel.init(product: product))
        }
        return Observable.just(viewModels)
    }

    func resetCalculate() {
        productCellViewModel = [ProductCellViewModel]()
    }
}

目标是能够在调用 viewmodel.requestData() 之后测试所有 ProductsViewState

【问题讨论】:

  • 如果您发布可编译的代码会有所帮助。
  • 感谢您的回答。我认为您需要的所有内容都已发布。您正在请求产品并且视图将显示(在我的视图模型中转换的加载、错误、空或视图模型单元数组),所以我必须测试所有这些视图状态,但我不知道该怎么做。顺便说一句,告诉我你需要什么,我会尝试添加它。再次感谢
  • 您要求我们使上述代码可编译,以便我们为您编写测试?你如何做前半部分并发布可编译的代码。
  • 为了帮助您,我编辑了您的问题以包含您项目中的最小可编译示例。

标签: mvvm rx-swift rxtest rx-blocking


【解决方案1】:

这里的关键是你必须将你的调度器注入到函数中,这样你才能注入一个测试调度器。然后您将能够测试您的state。顺便说一句,state 属性应该是 let 而不是 var。

class ProductsViewModelTests: XCTestCase {

    var scheduler: TestScheduler!
    var result: TestableObserver<ProductsViewState>!
    var disposeBag: DisposeBag!

    override func setUp() {
        super.setUp()
        scheduler = TestScheduler(initialClock: 0)
        result = scheduler.createObserver(ProductsViewState.self)
        disposeBag = DisposeBag()
    }

    func testStateLoaded() {
        let mockRepo = MockProductsRepository(products: { .empty() }, promotions: { .empty() })
        let viewModel = ProductsViewModel(repository: mockRepo)

        viewModel.state.bind(to: result).disposed(by: disposeBag)
        viewModel.requestData(scheduler: scheduler)

        scheduler.start()

        XCTAssertEqual(result.events, [.next(0, ProductsViewState.loading), .next(1, .loaded([]))])
    }

    func testState_ProductsError() {
        let mockRepo = MockProductsRepository(products: { .error(StubError()) }, promotions: { .empty() })
        let viewModel = ProductsViewModel(repository: mockRepo)

        viewModel.state.bind(to: result).disposed(by: disposeBag)
        viewModel.requestData(scheduler: scheduler)

        scheduler.start()

        XCTAssertEqual(result.events, [.next(0, ProductsViewState.loading), .next(1, .error)])
    }

    func testState_PromotionsError() {
        let mockRepo = MockProductsRepository(products: { .empty() }, promotions: { .error(StubError()) })
        let viewModel = ProductsViewModel(repository: mockRepo)

        viewModel.state.bind(to: result).disposed(by: disposeBag)
        viewModel.requestData(scheduler: scheduler)

        scheduler.start()

        XCTAssertEqual(result.events, [.next(0, ProductsViewState.loading), .next(1, .error)])
    }
}

struct StubError: Error { }

struct MockProductsRepository: ProductsRepository {
    let products: () -> Observable<Products>
    let promotions: () -> Observable<[Promotion]>

    func fetchProducts() -> Observable<Products> {
        return products()
    }

    func fetchPromotions() -> Observable<[Promotion]> {
        return promotions()
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-12
    • 2012-09-25
    • 2023-04-03
    • 2019-10-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多