Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
10
Help us understand the problem. What is going on with this article?
@ktanaka117

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
    }
}
10
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ktanaka117
百合好きのダンボールの人です。 SwiftでiOSアプリを開発していて、最近のホットトピックはテスト、設計、リファクタリング。 Twitter: @ktanaka117

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
10
Help us understand the problem. What is going on with this article?