iOSで非同期処理のテスト:  XCTWaiterとXCTWaiterDelegateで、非同期処理の結果をハンドリングする

iOSの非同期処理のテストは、 `XCTestExpectation` クラスのインスタンスをwaitすることで実現できます。以下の記事では `XCTestExpectation` 側から解説しましたが、今記事では `XCTWaiter` と `XCTWaiterDelegate` 側から解説します。

[iOSで非同期処理のテスト: XCTestExpectationの設定値と使い方まとめ \- Qiita](https://qiita.com/ktanaka117/items/e452e5ca58545c303dc2)

## XCTestExpectationがfulfill()されることを期待する

`wait(for:timeout:)`

使い方は以下の通り。 `XCTestExpectation` のインスタンスを、タイムアウト設定とともにwaitし、非同期処理のコールバックで `fulfill()` されればテストが通ります。

```swift
func testHoge() {
    let exp = expectation(description: "Hoge")
    
    someAsynchronousFunction {
        exp.fulfill()
    }
    
    wait(for: [exp], timeout: 10.0)
}
```

## XCTestExpectationがwaitした順にfulfill()されることを期待する

`wait(for:timeout:enforceOrder:)`

複数の `XCTestExpectation` インスタンスが、waitした(配列に格納した)順序で `fulfill()` されることを期待する場合は、このメソッドを使います。引数 `enforceOrder:` にtrueを渡すと有効化できます。

```swift
// 順序を無視してfulfill()したため、テストが失敗する
func testHoge() {
    let exp1 = expectation(description: "Hoge")
    let exp2 = expectation(description: "Fuga")
    
    someAsynchronousFunction {
        exp2.fulfill() // <-
        exp1.fulfill() // <- fulfillする順番を1と2で入れ替えれば、テストは成功する
    }
    
    wait(for: [exp1, exp2], timeout: 10.0, enforceOrder: true) // <- for: の配列に格納した順序
}
```

## XCTWaiterDelegateの各メソッド

`XCTWaiterDelegate` には、 `XCTestExpectation` のwait処理中に発生した例外を処理するdelegateメソッドが存在します。 `XCTestCase` は `XCTWaiterDelegate` に準拠しているため、delegateメソッドが呼び出された時の委譲先になります。

各例外と、その例外が発生する条件を紹介していきます。 `XCTestExpectation` の設定値と使い方の詳細については以下の記事をご覧ください。

[iOSで非同期処理のテスト: XCTestExpectationの設定値と使い方まとめ \- Qiita](https://qiita.com/ktanaka117/items/e452e5ca58545c303dc2)

### XCTestExpectationがタイムアウトした時に呼ばれるメソッド

`XCTestCase` のサブクラスでOverrideして使うことができます。

```swift
func waiter(_ waiter: XCTWaiter, didTimeoutWithUnfulfilledExpectations unfulfilledExpectations: [XCTestExpectation])
```

以下のコードで呼び出しが確認できます。 `XCTestExpectation` のインスタンスを `fulfill()` せず、設定した時間がきてタイムアウトしてしまうコードです。

```swift
// fulfill()されていないのでタイムアウトで失敗する
func testHoge() {
    let exp = expectation(description: "Hoge")
    
    someAsynchronousFunction {
        // do nothing
    }
    
    wait(for: [exp], timeout: 10.0) // <- Asynchronous wait failed: Exceeded timeout of 10 seconds, with unfulfilled expectations: "Hoge".
}

override func waiter(_ waiter: XCTWaiter, didTimeoutWithUnfulfilledExpectations unfulfilledExpectations: [XCTestExpectation]) {
    super.waiter(waiter, didTimeoutWithUnfulfilledExpectations: unfulfilledExpectations)
    
    // プリントされる
    print("didTimeoutWithUnfulfilledExpectations!!!")
}
```

### 期待する結果を反転したXCTestExpectationを `fulfill()` した時に呼ばれるメソッド

`XCTestCase` のサブクラスでOverrideして使うことができます。

```swift
func waiter(_ waiter: XCTWaiter, didFulfillInvertedExpectation expectation: XCTestExpectation)
```

以下のコードで呼び出しが確認できます。 `XCTestExpectation` のインスタンスに `isInverted = true` を設定することで、期待する結果を反転させることができます。その状態で `fulfill()` を呼んでしまい、テストが失敗するコードです。

```swift
// isInverted = trueになっている状態でfulfill()して失敗する
func testHoge() {
    let exp = expectation(description: "Hoge")
    exp.isInverted = true
    
    someAsynchronousFunction {
        exp.fulfill()
    }
    
    wait(for: [exp], timeout: 10.0) // <- Fulfilled inverted expectation "Hoge".
}

override func waiter(_ waiter: XCTWaiter, didFulfillInvertedExpectation expectation: XCTestExpectation) {
    super.waiter(waiter, didFulfillInvertedExpectation: expectation)

    // プリントされる
    print("didFulfillInvertedExpectation!!!")
}
```

### XCTestExpectationをwaitした順序を守らなかった時に呼ばれるメソッド

`XCTestCase` のサブクラスでOverrideして使うことができます。

```swift
func waiter(_ waiter: XCTWaiter, fulfillmentDidViolateOrderingConstraintsFor expectation: XCTestExpectation, requiredExpectation: XCTestExpectation)
```

以下のコードで呼び出しが確認できます。 `wait(for:timeout:enforceOrder:)` メソッドを使い、 `enforceOrder: true` とすることで、引数に渡した配列に格納されている `XCTestExpectation` のインスタンスが、格納されている順に `fulfill()` されなかった場合に、例外を発生させることができます。

```swift
// 順番通りにfulfill()しなかったために失敗する
func testHoge() {
    let exp1 = expectation(description: "Hoge")
    let exp2 = expectation(description: "Fuga")
    
    someAsynchronousFunction {
        exp2.fulfill()
        exp1.fulfill() // <- Failed due to expectation fulfilled in incorrect order.
    }
    
    wait(for: [exp1, exp2], timeout: 10.0, enforceOrder: true)
}

override func waiter(_ waiter: XCTWaiter, fulfillmentDidViolateOrderingConstraintsFor expectation: XCTestExpectation, requiredExpectation: XCTestExpectation) {
    super.waiter(waiter, fulfillmentDidViolateOrderingConstraintsFor: expectation, requiredExpectation: requiredExpectation)

    // プリントされる
    print("fulfillmentDidViolateOrderingConstraintsFor!!!")
}
```

## テストの結果を表すXCTWaiter.Result

`XCTWaiter` には、テストの結果を表す `Result` というenumが存在します。それぞれのケースが何を示すのか、以下にコメントでまとめました。

```swift
extension XCTWaiter {
    public enum Result : Int {
        // 全てのexpectationが正常にfulfillされた状態
        case completed

        // expectationが処理される前にタイムアウトされた状態
        case timedOut

        // expectationが順序通りにfulfillされなかった状態
        case incorrectOrder

        // 期待する結果が反転されたexpectationをfulfillした状態
        case invertedFulfillment

        // 他のwaiterに割り込まれた状態
        case interrupted
    }
}
```