Тест PublishSubject для ViewState - PullRequest
1 голос
/ 03 июня 2019

Я пытаюсь проверить основную функциональность моего 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]()
    }
}

Цель состоит в том, чтобы иметь возможность протестировать все ProductsViewState после вызова viewmodel.requestData ()

1 Ответ

1 голос
/ 08 июня 2019

Ключевым моментом здесь является то, что вы должны внедрить свой планировщик в функцию, чтобы вы могли внедрить планировщик теста.Тогда вы сможете проверить свой 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()
    }
}
...