Mockingjayを使ったAPI通信のユニットテスト
テストの中でもAPI通信は手動でやるには大変な領域ではないでしょうか
ケースごとにサーバの値や通信状態を変えるのはやりたくないですよね
調べてみたところ、Mockingjayというライブラリがどうも良さそうだということで、使い方をまとめてみようと思います
Mockingjayとは
https://github.com/kylef/Mockingjay
HTTP/HTTPS通信をスタブに置き換えてくれるSwiftのテスティングライブラリです。URLConnection
またはURLSession
を使用している場合に適用できます。
私はAlamofireを使っていますが、内部的にURLSession
を使用しているため、Mockingjayによってスタブ化することができます。
Alamofire, URLSessionの通信処理をMethod Swizzlingでスタブに置き換える
こちらの記事を見るに、Method Swizzlingによってスタブ化を実現しているようです。
Method Swizzlingは既存のメソッドを別のものに置き換えてしまう強力な機能です。通信に限らずテストではとても活躍しそうですね!
使い方
Mockingjayをimportした上で、テストコード中でstub
メソッドを呼び出します。シンプル!
import Mockingjay
...
stub(/* matcher(スタブ化したい通信の指定) */, /* builder(返す結果) */)
matcher
とbuilder
組み込みのもの
スタブ化したい通信の指定をmatcher
と呼んでいます。
組み込みで3種類のmatcher
が用意されています。
-
everything
: すべての通信 -
uri(template)
: URIテンプレートを使用した指定 -
http(method, template)
: HTTPメソッドとURIテンプレートを使用した指定
返す結果はbuilder
と呼ばれています。
組み込みで4種類のbuilder
が用意されています。
-
failure(error)
: 通信失敗 -
http(status, headers, data)
: httpレスポンス -
json(body, status, headers)
: シリアライズ化されたjsonデータ(String
) -
jsonData(data, status, headers)
: 生のjsonデータ(Data
)
この2つを組み合わせて通信をスタブ化します。
// すべての通信で404を返す
stub(everything, http(status: 404))
// 指定されたURIへのPUTリクエストでbody0を返す
let body0 = [ "description": "Kyle" ]
stub(http(.put, uri: "https://github.com/kylef/Mockingjay"), json(body0))
// 指定されたURIへのGETリクエストでbody1を返す
let body1 = "{ \"parse\": \"error\" }".data(using: .utf8)!
stub(uri("https://github.com/kylef/Mockingjay"), jsonData(body1))
自作する
matcher
とbuilder
はそれぞれ以下のように定義されています。
public typealias Matcher = (URLRequest) -> (Bool)
public typealias Builder = (URLRequest) -> (Response)
これらの定義に適合するメソッドを作成すれば、任意のmatcher
とbuilder
を使用できます。
XCTestExpectation
を使用して非同期を待つ
API通信は非同期処理のため、何も考えずにテストを書いても、通信処理が終わる前にテストが終了してしまいます。通信処理が完了するのを待つために、XCTestExpectation
を使います。
私はAPI通信テスト用の便利メソッドを作成して使っています。Rxを使っていたり、クラス名が一部適当ですが、雰囲気で理解してください
private func request(requestInfo: SomeInfoClass, onSuccess: @escaping (Data) -> Void, onError: @escaping (Error) -> Void) {
let expectation = self.expectation(description: "network") // XCTestExpectation生成
someNetworkClass.request(requestInfo).subscribe( // 通信開始
onSuccess: { data in
onSuccess(data) // 通信成功時に実行するテスト
expectation.fulfill() // 非同期処理が完了したことを通知
},
onError: { error in
onError(error) // 通信失敗時に実行するテスト
expectation.fulfill() // 非同期処理が完了したことを通知
}).disposed(by: self.disposeBag)
self.waitForExpectations(timeout: 1, handler: nil) // 非同期処理完了通知が来るまで待つ(タイムアウト1秒)
}
テストサンプル
これまで紹介したものを組み合わせて、以下のようなテストを書いています。
func testSample() {
// スタブ化
let body0 = [ "description": "Kyle" ]
stub(http(.put, uri: "https://github.com/kylef/Mockingjay"), json(body0))
// 通信処理の実行
self.request(
requestInfo: /* urlやパラメータなどを含んだクラス */,
onSuccess: { data in
// テスト実行
let actual = /* jsonのパース */
let expected = /* 期待するデータの作成 */
XCTAssertEqual(actual, expected)
},
onError: { error in
// このテストは通信失敗しないはずなので、入ってきたらテスト失敗するようにしておく
XCTFail("never reach here")
})
}
終わりに
冒頭にも少し書きましたが、通信処理のテストは書く価値がかなり高いテストだと思います
もしすべてを自力で書こうとしたら結構大変ですが、Mockingjayを利用することで割と楽にテストを書くことができます
どんどん書いていきましょう
参考
HTTPモックライブラリ「Mockingjay」を使ってみた話/swift-mockingjay
SwiftのQuickでAlamofireを使った非同期のテストを書く
Author And Source
この問題について(Mockingjayを使ったAPI通信のユニットテスト), 我々は、より多くの情報をここで見つけました https://qiita.com/Yaruki00/items/917cef757109f07a4f17著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .