15
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

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
    }
}
15
14
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
15
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?