LoginSignup
1
1

More than 3 years have passed since last update.

[XCTest] 非同期処理のテストについて備忘録

Last updated at Posted at 2021-05-20

背景

URLSessionで作ったApiClientクラスのfetch処理をAlamofireを使った実装にリプレイスすることになり、
今回はテストにも慣れてきたのでTDDで進めようと試みました。

第1段階

元の関数でAPIのレスポンスをパースしてItemを10個拾うテストを書きます。
これは「テストがうまく動作するかどうか」のテストのような段階です。

ApiClientTests.swift
class ApiClientTests: XCTestCase {
  func testFetchItems() throws {
    var tenItems: [Item]?

    // URLSessionを使った
    // fetchItems(url: String, completion: @escaping ([Item]?) -> Void)
    ApiClient.fetchItems(url: "http://...10個返ってくるAPI...") { items in
      tenItems = items
    }
    sleep(4)
    XCTAssertEqual(tenItems.count, 10)
  }
}
// テスト成功♪

第2段階

Apiclient.fetchItems2という関数名でAlmofire版を実装する前にテスト側を書き直します。

ApiClientTests.swift
class ApiClientTests: XCTestCase {
  func testFetchItems() throws {
    var tenItems: [Item]?

    // Alamofireを使った
    // fetchItems2(url: String, completion: @escaping ([Item]?) -> Void)
    ApiClient.fetchItems2(url: "http://...10個返ってくるAPI...") { items in
      tenItems = items
    }
    sleep(4)
    XCTAssertEqual(tenItems.count, 10)
  }
}
// まだfetchItems2は実装していないのでテストは失敗

第3段階ここで問題発生

Apiclient.fetchItems2という関数名でAlmofire版を実装したのにテストがうまくいきません。

ApiClientTests.swift
class ApiClientTests: XCTestCase {
  func testFetchItems() throws {...}
}
// tenItemsがnilでエラーになる。

テストではなく実際にアプリを動かすと問題なく動くのですが、テストだけ失敗してしまう。
調査すると非同期がうまく動作していないようで、testFetchItems()の処理後にtenItems = itemsの処理がされているようです。

非同期処理のテストにはXCTestExpectation

XCTestExpectationとはXCTestにおいて処理を一旦止めて非同期で走っている処理の完了のお知らせfulfill()を待ってまた走り出すという動きができます。
これでテストもうまく動作するようになりました。

ApiClientTests.swift
class ApiClientTests: XCTestCase {
  func testFetchItems() throws {
    var tenItems: [Item]?

    // 待ち合わせ用
    let expectation = expectation(description: "非同期待ち")

    // Alamofireを使った
    // fetchItems2(url: String, completion: @escaping ([Item]?) -> Void)
    ApiClient.fetchItems2(url: "http://...10個返ってくるAPI...") { items in
      tenItems = items
      expectation.fulfill() // ここで完了のお知らせ
    }

    // ここで完了のお知らせを待ちます。
    // timeout: 4 とすると4秒待ってお知らせがなければテスト失敗として処理してくれます。
    waitForExpectations(timeout: 4) 

    XCTAssertEqual(tenItems.count, 10)
  }
}
// 無事テスト成功♪

リプレイス自体は完了したが、一部の矛盾が解決しませんでした。

無事にリプレイスは完了して、非同期のテストについても問題なく実装できるようになったことにウキウキしていました。
しかし今度は第一段階のテストがなぜ成功していたのかが不明です。
これはまだ解決していないのですが、優先順位が低いので一旦後回しにします。
この記事書いた目的は「忘れ去ってしまいそうなので備忘録」として残したのもあるのですが、「あわよくば通りすがりの優しいつよつよエンジニアさんが知見をこぼしてくれたりしないだろうか」なんて期待もしてたりします。

参考にした記事や書籍

@takehito-koshimizu
ありがとうございました🙏
今回の解決には至りませんでしたが、URLSessionのキャッシュ機能について知見が広がりました🤗

もし今後解決したら追記予定とします。

以上!!

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1