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())
}
}
}
}
}
- 指定の Host へのアクセスを GET_test.json に Stub しています。
- json は
{"response": true}
という感じのを置いています。
- json は
- API を実行します。
- done() を呼んで処理を終了させます。
-
waitUntil { done in done() }
を使い、処理が完了するのを待っています。
-
- 最後に、
it
内で response が true になっていればテストが通ります。
API を Stub しテストを実行するのはざっくり上記の流れでできます
↑のやり方だと複数エンドポイントの 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 を返す事が出来ました
また、処理をラップした事で 1 行で Stub することが出来ました
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 など書けるところはしっかりと書いていきましょう!