Swift2 - Quick + Mockingjay で Alamofire の非同期通信のテストをする
API との通信をテストする時、実際にリクエストを投げてしまうとサーバーへの負荷やテスト時間など、コストが多くかかってしまうので、Alamofire のリクエストを Quick と Mockingjay を使ってスタブする方法を紹介したいと思います。
- Quick: RSpec 風に書ける単体テストライブラリ Quick
- Mockingjay: URL リクエストをモックできるライブラリ Mockingjay
以前書いた Swift2 Alamofire + ObjectMapper で API クライアントを作成する のリクエストに対してテストを書いてみたいと思います。テストするリクエストは以下です。
API.call(Endpoint.User.Find(id: 1)) { response in switch response { case .Success(let result): print("success \(result)") case .Failure(let error): print("failure \(error)") } }
import Alamofire import ObjectMapper class Endpoint { enum User: RequestProtocol { typealias ResponseType = UserEntity case Find(id: Int) var method: Alamofire.Method { switch self { case .Find: return .GET } } var path: String { switch self { case .Find(let id): return "/users/\(id)" } } } }
import ObjectMapper class UserEntity: Mappable { var id: Int? var name: String? var email: String? var url: String? required convenience init?(_ map: Map) { self.init() } func mapping(map: Map) { id <- map["id"] name <- map["name"] email <- map["email"] url <- map["url"] } }
- テストを書く
- スタブ用の json ファイルを用意する
- json ファイルから NSDate に変換する処理は繰り返し利用する為 Helper クラスを用意する
1. テストを書く
テストの完成形は以下の様になります。
- Quick には toEventually という非同期の処理様のテストメソッドがあるのでそれを利用します
- Class のプロパティとしてテスト対象のデータを宣言しないと上手くtoEventually メソッドが使えません
self.stub のメソッドが Mockingjay によるスタブです。 jsonData で指定した data がレスポンスとして返ります
UserSpec
import Quick import Nimble import Mockingjay import ObjectMapper @testable import projectName class UserSpec: QuickSpec { var user: UserEntity? var stubUser: UserEntity? var error: NSError? override func spec() { describe("Endpoint.User.Find") { context("正常系") { beforeEach { let data = SpecHelper.readJSONFile("parent") self.stub(http(.GET, uri: "/users/1"), builder: jsonData(data)) let JSONString = String(data: data, encoding: NSUTF8StringEncoding) self.stubUser = Mapper<UserEntity>().map(JSONString) } afterEach { self.user = nil self.stubUser = nil self.removeAllStubs() } it("指定したユーザー情報が取得できること") { API.call(Endpoint.User.Find(id: 1)) { response in switch(response) { case .Success(let result): self.user = result case .Failure(let error): print(error) } } expect(self.user!.id).toEventually(equal(self.stubUser!.id)) expect(self.user!.name).toEventually(equal(self.stubUser!.name)) expect(self.user!.email).toEventually(equal(self.stubUs!.email)) expect(self.user!.url).toEventually(equal(self.stubUs!.url)) } } context("異常形") { context("APIがメンテナンス中") { beforeEach { self.stub(http(.GET, uri: "/users/1"), builder: http(503)) } afterEach { self.removeAllStubs() } it("503 エラーが返却されること") { API.call(Endpoint.PutFavorite(id: facilityId)) { response in switch response { case .Success(_): break case .Failure(let error): self.error = error } } expect(self.error?.userInfo.description).toEventually(equal("[NSLocalizedFailureReason: Response status code was unacceptable: 503]")) expect(self.error?.code).toEventually(equal(-6003)) } } } } } }
2. スタブ用の json ファイルを用意する
let data = SpecHelper.readJSONFile("parent") self.stub(http(.GET, uri: "/users/1"), builder: jsonData(data))
Mockingjay では NSData を jsonData に渡してあげることで uri に指定したリクエストをスタブし data をレスポンスとして返してくれます。 json データは記述が多くなることが多いのでレスポンス用の json ファイルを作成します。
- user.json
{ "id": 1, "name": "ティナ・ブランフォード", "email": "tina@example.com", "url": "https://ja.wikipedia.org/wiki/ファイナルファンタジーVI" }
3. json ファイルから NSDate に変換する処理は繰り返し利用する為 Helper クラスを用意する
用意しておくと便利です。
- SpecHelper.swift
static func readJSONFile(name: String) -> NSData { let path: String? = NSBundle(forClass: self).pathForResource(name, ofType: "json") let fileHandle: NSFileHandle? = NSFileHandle(forReadingAtPath: path!) let data: NSData! = fileHandle?.readDataToEndOfFile() return data }