我正在尝试测试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
答案 0 :(得分: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()
}
}