测试 ViewState 的 PublishSubject
Test PublishSubject for ViewState
我正在尝试测试我的 ViewModel 的主要功能。重要的一步是测试加载状态完成。但可以肯定的是,为了更好的测试,测试所有状态可能会很有趣。
我阅读了很多 post 和有关 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
这里的关键是你必须将你的调度程序注入到函数中,这样你才能注入一个测试调度程序。然后你就可以测试你的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()
}
}
我正在尝试测试我的 ViewModel 的主要功能。重要的一步是测试加载状态完成。但可以肯定的是,为了更好的测试,测试所有状态可能会很有趣。
我阅读了很多 post 和有关 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
这里的关键是你必须将你的调度程序注入到函数中,这样你才能注入一个测试调度程序。然后你就可以测试你的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()
}
}