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

[Classi Advent Calendar 2016](http://qiita.com/advent-calendar/2016/classi) 1 日目です。
現在開発中の iOS アプリで [Quick](https://github.com/Quick/Quick) を使っており、そこで実施している API のテストについて書いていきます。

# [Quick](https://github.com/Quick/Quick) とは

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

XCTest: 

```swift
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:

```swift
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について説明する](http://qiita.com/akatsuki174/items/0ed315456d267cde19f4) でわかりやすく説明されています。


# API のテスト

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

## API を Stub しテストする

API のテストではあらかじめ json ファイルを用意し、レスポンスを Stub します。
API の Stub のために [AliSoftware/OHHTTPStubs](https://github.com/AliSoftware/OHHTTPStubs) を利用します。

```swift
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 を作成すると便利です。


```swift
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)
            }
        )
    }
}
```

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

```swift
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` で指定する事ができます。

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

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

# 最後に

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

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