iOSで非同期処理のテスト: XCTestExpectationの設定値と使い方まとめ

## XCTestExpectationの基本の使い方

`XCTestExpectation` は `XCTest` フレームワークで非同期処理をテストする際に用いられます。基本の使い方は以下の通り、非同期処理を行う関数のコールバック内で `fulfill()` を呼び出し、 `wait(for:timeout:)` することで、待機するexpectationとそれのタイムアウトを設定します。

```swift
func someAsynchronousFunction(completion: () -> ()) {
    // do some asynchronous tasks
    completion()
}

func testHoge() {
    let exp = expectation(description: "Hoge")

    someAsynchronousFunction {
        // callback
        exp.fulfill()
    }

    wait(for: [exp], timeout: 10.0)
}
```

## fulfill()する回数を指定する

`wait(for:timeout:)` で `XCTestExpectation` のインスタンスをwaitした場合、 デフォルトで `fulfill()` をテスト終了までに一回呼び出さなければなりません。この時、呼び出しが0回でも、2回以上でもテストが失敗します。

```swift
// 呼び出し回数が0回で失敗(タイムアウト)
func testHoge() {
    let exp = expectation(description: "Hoge")
    
    someAsynchronousFunction {
        // callback
    }
    
    wait(for: [exp], timeout: 10.0) // <- Asynchronous wait failed: Exceeded timeout of 10 seconds, with unfulfilled expectations: "Hoge".
}
```

```swift
// 呼び出し回数が2回で失敗(複数回fulfillが呼ばれている)
func testHoge() {
    let exp = expectation(description: "Hoge")
    
    someAsynchronousFunction {
        // callback
        exp.fulfill()
        exp.fulfill()
    }
    
    wait(for: [exp], timeout: 10.0) // <- caught "NSInternalInconsistencyException", "API violation - multiple calls made to -[XCTestExpectation fulfill] for Hoge."
}
```

もしも複数回の `fulfill()` 呼び出しを期待するテストが書きたい場合は、 `expectedFulfillmentCount` プロパティに期待する呼び出し回数を指定します。

```swift
// 呼び出し回数を2回で指定して成功する
func testHoge() {
    let exp = expectation(description: "Hoge")
    exp.expectedFulfillmentCount = 2
    
    someAsynchronousFunction {
        // callback
        exp.fulfill()
        exp.fulfill()
    }
    
    wait(for: [exp], timeout: 10.0)
}
```

`expectedFulfillmentCount` プロパティはデフォルトの値が1です。もしも指定した回数を下回るか、超えてしまう場合はテストが失敗します。

## fulfill()の呼び出し回数の上限を無くす

`expectedFulfillmentCount` で指定した回数を超えて `fulfill()` を呼び出すと、テストが失敗してしまうのは先述した通りです。この呼び出し回数の上限を無くすには `assertForOverFulfill` プロパティをfalseにします。

```swift
// 呼び出し回数が指定回数を超えても成功する
func testHoge() {
    let exp = expectation(description: "Hoge")
    exp.expectedFulfillmentCount = 2
    exp.assertForOverFulfill = false
    
    someAsynchronousFunction {
        // callback
        exp.fulfill()
        exp.fulfill()
        exp.fulfill() // <- expectedFulfillmentCountは2だが、3回目のfulfill()を呼んでもテストは失敗しない
    }
    
    wait(for: [exp], timeout: 10.0)
}
```

`assertForOverFulfill` プロパティはデフォルトではtrueになっています。

## 期待する結果を反転させる

`XCTestExpectation` で期待する結果を反転させるには、 `isInverted` プロパティをtrueにします。デフォルトはfalseです。

`XCTestExpectation` のインスタンスをwaitして、waitしている間に `fulfill()` されればテストが成功するというのが通常の挙動です。期待する結果を反転させるというのは、 `fulfill()` された場合、テストが失敗するということです。

`isInverted` プロパティがtrueで、 `fulfill()`　を呼び出してテストが失敗した場合、 `XCTWaiterDelegate` のメソッドが呼び出されます。( `waiter(_ waiter:didFulfillInvertedExpectation expectation:)` )

```swift
// 期待する結果を反転させたexpをfulfill()して失敗する
func testHoge() {
    let exp = expectation(description: "Hoge")
    
    someAsynchronousFunction {
        exp.isInverted = true
        exp.fulfill() // <- Fulfilled inverted expectation "Hoge".
    }
    
    wait(for: [exp], timeout: 10.0)
}
```

`isInverted` プロパティをtrueにした `XCTestExpectation` のインスタンスをwaitして、テストがタイムアウトした場合、テストは成功します。このとき、 `XCTWaiterDelegate` のタイムアウト失敗時のdelegateメソッドは呼び出されません。( `waiter(_ waiter:didTimeoutWithUnfulfilledExpectations unfulfilledExpectations:)` )

```swift
// isInverted = trueとなっているので、タイムアウトしてもテストが成功する
func testHoge() {
    let exp = expectation(description: "Hoge")
    
    someAsynchronousFunction {
        exp.isInverted = true
    }
    
    wait(for: [exp], timeout: 10.0)
}
```