iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 23
0
Mobile Development

RxSwift / 30天探索之旅系列 第 23

第 23 天 - RxBlocking & RxTest 範例

  • 分享至 

  • xImage
  •  

昨天講了RxBlocking和RxTest,今天就將兩天前所寫的範例,加上測試程式吧!

宣告

var viewModel: ProductListViewModel!

viewModel就是待會要測試的對象

測試程式的setUp()tearDown()

override func setUp() {
    super.setUp()

    let apiService = TestAPI()
    viewModel = ProductListViewModel(apiService: apiService)
}

override func tearDown() {
    super.tearDown()
}
  1. setUp()時,建立測試的API,並且注入到ViewModel當中,另外,初始化TestScheduler

測試ViewModel初始化

原始版本

func test_init_load_data() {
    let disposeBag = DisposeBag()
    // 1
    let expect = expectation(description: #function)
	  
    var result: [Product]!
    
    viewModel.data.asObservable()
        .skip(1) // 2
        .subscribe(onNext: {
            // 3
            result = $0
            expect.fulfill()
        })
        .disposed(by: disposeBag)
    // 4
    waitForExpectations(timeout: 5.0) { error in
        guard error == nil else {
            XCTFail(error!.localizedDescription)
            return
        }
        // 5
        XCTAssertEqual(20, result.count)
    }
}
  1. expectation會建立一個XCTestExpectation,可用來測試非同步狀況,因為非同步並非由我們指定的步驟進行,所以,我們在預期的會經過的地方,放expect.fulfill(),執行到這行的話,就代表達到我們預期。
  2. 因為data為BehaviorRelay,在訂閱後會拿到最一開始會得的[],所以我們skip(1)
  3. 把結果儲存,並且執行expect.fulfill(),表示符合我們預期
  4. 因為call API需要時間,這邊我們撰寫waitForExpectations(timeout: 5.0)來表示等待5秒,若在5秒內都沒有執行到expect.fulfill(),那測試就會失敗
  5. 這邊我預期會得到20筆資料,這是定義在TestAPI()中的假資料

RxBlocking版本
嘗試以RxBlocking測試ViewModel初始化

func test_init_load_data_ver_blocking() throws {
    let disposeBag = DisposeBag()
    // 1
    let observable = viewModel.data.skip(1).map { $0.count }
    // 2
    viewModel.data.subscribe().disposed(by: disposeBag)
    // 3
    XCTAssertEqual(try observable.toBlocking().first(), 20)
}
  1. 這邊寫法就簡單多,不用透過XCTestExpectation
  2. 這邊其實可寫可不寫,因為data是BehaviorRelay,所以,就算不訂閱也能發送元素,反之,若是需要訂閱才能發送的Observable,就必須寫這段
  3. 使用first()來獲取第一個元素,預期會得到20筆

測試Pull to Refresh

func test_pull_to_refresh() throws {
    let disposeBag = DisposeBag()
    // 1
    viewModel.data.skip(1).take(1)
        .subscribe(onNext: { _ in
            self.viewModel.triggerAPI.onNext(()) // 2
        })
        .disposed(by: disposeBag)
    // 3
    let observable = viewModel.data.skip(2).map { $0.count }
    // 4
    XCTAssertEqual(try observable.toBlocking().first(), 20)
}
  1. 使用skip(1)略過BehaviorRelay的初始值,使用take(1)來抓取第二個元素,也就是初始化後會call API,這時data會收到第二個元素
  2. 這時我們向viewModel.triggerAPI發送元素,也就會執行reload
  3. 我們要取得data的第三個元素,也就是第2步驟所reload後,得到的值
  4. 轉換成BlockingObservable,預期得到20筆

測試Next Page

func test_betch_reload() throws {
    let disposeBag = DisposeBag()

    viewModel.data.skip(1).take(1)
        .subscribe(onNext: { _ in
            self.viewModel.triggerNextPage.onNext(()) // 1
        })
        .disposed(by: disposeBag)

    let observable = viewModel.data.skip(2).map { $0.count }
    // 2
    XCTAssertEqual(try observable.toBlocking().first(), 21)
}
  1. 同樣的,只是改成向viewModel.triggerNextPage發送元素
  2. 這時我們預期得到21筆

本來嘗試在使用RxTest,但最終還是沒嘗試出來,若有大大會的話,也請指教一下,就這樣,明天見!


上一篇
第 22 天 - RxBlocking & RxTest
下一篇
第 24 天 - Reactive Extensions
系列文
RxSwift / 30天探索之旅30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言