iOS
テスト
XCTest
Swift

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

More than 1 year has passed since last update.

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

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


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

wait(for:timeout:)

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

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を渡すと有効化できます。

// 順序を無視して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メソッドが存在します。 XCTestCaseXCTWaiterDelegate に準拠しているため、delegateメソッドが呼び出された時の委譲先になります。

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

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


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

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

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

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

// 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して使うことができます。

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

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

// 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して使うことができます。

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

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

// 順番通りに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が存在します。それぞれのケースが何を示すのか、以下にコメントでまとめました。

extension XCTWaiter {

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

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

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

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

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