Help us understand the problem. What is going on with this article?

【Swift】Quick + OHHTTPStubs で API のテストを書いていく

More than 3 years have passed since last update.

Classi Advent Calendar 2016 1 日目です。
現在開発中の iOS アプリで Quick を使っており、そこで実施している API のテストについて書いていきます。

Quick とは

Xcode でプロジェクトを作ると XCTest がデフォルトのテストフレームワークとなっています。
ですが、 Quick を使うと rspec のように書けるため好んで利用しています。

XCTest:

class DateExtensionXCTest: XCTestCase {
    let date = Date(timeIntervalSince1970: TimeInterval(0))
    func yyyy年MM月dd日_yyyy年MM月dd日のフォーマットになっていること() {
        XCTAssertEqual(date.yyyy年MM月dd日, "1970年01月01日")
    }
}

Quick:

class DateExtensionSpec: QuickSpec {
    override func spec() {
        let date = Date(timeIntervalSince1970: TimeInterval(0))
        describe("yyyy年MM月dd日") {
            it("yyyy年MM月dd日 のフォーマットになっていること") {
                expect(date.yyyy年MM月dd日).to(equal("1970年01月01日"))
            }
        }
    }
}

具体的な違いについては XCTestと比較しつつQuickについて説明する でわかりやすく説明されています。

API のテスト

API Spec を書いていくときにいくつか困ったことがあったので、その解決策です。

API を Stub しテストする

API のテストではあらかじめ json ファイルを用意し、レスポンスを Stub します。
API の Stub のために AliSoftware/OHHTTPStubs を利用します。

class : QuickSpec {
    override func spec() {
        describe("test api を実行") {
            var response = false

            context("API にアクセスに成功した場合") {
                beforeEach {
                    // 1. 指定の Host へのアクセスを GET_test.json に Stub する。
                    _ = stub(condition: isHost("example.com")) { _ in
                        let stubPath = OHPathForFile("GET_test.json", type(of: self))
                        return fixture(filePath: stubPath!, headers: nil)
                    }

                    // done() を実行すると処理が終わる
                    waitUntil { done in
                        // 2. API を実行する
                        Session.send(TestingRequest()) { result in
                            switch result {
                            case .success(let r): response = r
                            case .failure(_): break
                            }
                            // 3. beforeEach が完了
                            done()
                        }
                    }
                }
                afterEach {
                    OHHTTPStubs.removeAllStubs()
                }

                it("response が GET_test.json と同様に true になっていること") {
                    expect(response).to(beTrue())
                }
            }
        }
    }
}
  1. 指定の Host へのアクセスを GET_test.json に Stub しています。
    • json は {"response": true} という感じのを置いています。
  2. API を実行します。
  3. done() を呼んで処理を終了させます。
    • waitUntil { done in done() } を使い、処理が完了するのを待っています。
  4. 最後に、 it 内で response が true になっていればテストが通ります。

API を Stub しテストを実行するのはざっくり上記の流れでできます :tada:

↑のやり方だと複数エンドポイントの Stub ができなくない?

↑のやり方は host だけの判断で Stub するため、複数 API を実行する場合上記の書き方ではできません。
その場合は下記のメソッドを使います。

  • OHHTTPStubs.stubRequests(passingTest: OHHTTPStubsTestBlock, withStubResponse: OHHTTPStubsResponseBlock)

このような HttpHelper を作成すると便利です。

class HttpHelper {
    let host = "example.com"

    static func stubRequest(path: String, file: String, status: Int32 = 200, headers: [String: String]? = nil) {
        OHHTTPStubs.stubRequests(
            passingTest: { (request) -> Bool in
                // path が一致していたら Stub する
                return request.url?.path == path
            }, withStubResponse: { (request) -> OHHTTPStubsResponse in
                // Stub 用の json ファイルを取得
                let stubPath = OHPathForFile(file, type(of: self))
                return OHHTTPStubsResponse(fileAtPath: stubPath!, statusCode: status, headers: headers)
            }
        )
    }
}

上記のヘルパーを使うと...

class MultiUserRequestSpec: QuickSpec {
    override func spec() {
        describe("ログインとuser情報を取得する API を実行") {
            context("/user/1, /user/2 どちらも成功した場合") {
                var response: (userName1: String, userName2: String) = ("", "")

                beforeEach {
                    // /users/1, users/2 を Stub する
                    HttpHelper.stubRequest(path: "/users/1", file: "user1.json")
                    HttpHelper.stubRequest(path: "/users/2", file: "user2.json")

                    waitUntil { done in
                        // API を実行
                        Session.send(MultiUserRequest()) { result in
                            switch result {
                            case .success(let r): response = r
                            case .failure(_): break
                            }
                            done()
                        }
                    }
                }
                afterEach {
                    OHHTTPStubs.removeAllStubs()
                }

                it("response が user1.json, user2.json と同一である事") {
                    expect(response.userName1).to(equal("高海千歌"))
                    expect(response.userName2).to(equal("渡辺曜"))
                }
            }
        }
    }
}

それぞれのエンドポイントが Stub され、 users/1, users/2 それぞれの json を返す事が出来ました :tada: :tada:
また、処理をラップした事で 1 行で Stub することが出来ました :blush:

API の実行を失敗させたい場合は?

Response Status を status で指定する事ができます。

HttpHelper.stubRequest(path: "/users/1", file: "500.json", status: 500)

これで Response Status Code: 500 の json を返す事ができます。

最後に

Quick で API テストをする際には上記のやり方で一通りできると思います。
API を Stub できると、 サーバサイドの開発が遅れたりサーバが止まっていてもアプリ側の開発が続けられるため、とても便利です。

UI 関連のテストはなかなか骨が折れますが、 Model など書けるところはしっかりと書いていきましょう!

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