Posted at
ClassiDay 1

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

More than 1 year has 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 など書けるところはしっかりと書いていきましょう!