post Image
Alamofire, URLSessionの通信処理をMethod Swizzlingでスタブに置き換える

通信処理のテストをモックにする時に、MockingjayというOSSを使っているのをよく見ます。
どうやって実現しているのかが気になったので、ソースを読んでみると、既存のメソッドの入れ替え (Method Swizzling)を行っていました。

意外と簡単に作れそうだったので、最小限のシンプルな実装で、URLSessionやAlamofireの通信をスタブに置き換えてみました。

ユニットテストで使えるテクニックだと思います。

スタブに置き換える通信の例はこちらです。
APIのサンプルとして JSON Test を利用します。

// URLSessionの場合
let url = URL(string: "http://echo.jsontest.com/key/value/one/two")!
URLSession.shared.dataTask(with: url) { data, response, error in
    let json = try! JSONSerialization.jsonObject(with: data!, options: .allowFragments)
    print(json)
}.resume()

// Alamofireの場合
Alamofire.request("http://echo.jsontest.com/key/value/one/two").responseJSON { response in
    print(response.result.value!)
}
// 別の書き方でもOK
SessionManager.default.request("http://echo.jsontest.com/key/value/one/two").responseJSON { response in
    print(response.result.value!)
}

実行するとこのような出力になります。

{
    key = value;
    one = two;
}

Method SwizzlingによるURLSessionConfiguration.defaultの入れ替え

今回はURLSessionConfiguration.defaultをモック用に置き換えるという方法で実装してみます。
Alamofire.requestSessionManager.defaultも内部で、URLSessionConfiguration.defaultを使用しています。

URLSessionConfigurationにextensionで入れ替え準備用のメソッドと入れ替えるプロパティを追加します。

public extension URLSessionConfiguration {

    // .defaultをモック用と入れ替えるメソッド
    public class func setupMockDefaultSessionConfiguration() {
        let defaultSessionConfiguration = class_getClassMethod(URLSessionConfiguration.self, #selector(getter: URLSessionConfiguration.default))
        let swizzledDefaultSessionConfiguration = class_getClassMethod(URLSessionConfiguration.self, #selector(getter: URLSessionConfiguration.mock))
        method_exchangeImplementations(defaultSessionConfiguration, swizzledDefaultSessionConfiguration)
    }

    // .defaultと入れ替えるプロパティ変数
    private dynamic class var mock: URLSessionConfiguration {
        let configuration = self.mock
        configuration.protocolClasses?.insert(MockURLProtocol.self, at: 0)
        URLProtocol.registerClass(MockURLProtocol.self)
        return configuration
    }
}

method_exchangeImplementationsというAPIを使用して.default.mockを入れ替えています。

URLSessionConfigurationprotocolClassesに独自のURLProtocolを指定することで、通信のハンドリングをカスタマイズすることができます。
MockURLProtocolはこの次に追加するクラスです。
URLProtocol.registerClassは独自のプロトコルを認識させるために必要なメソッドです。

(.mockdynamicが付いていますが、こちらはprivateなメソッドをSelector型として取り出せるようにするためです。)

モック用のURLProtocolの定義

実装の入れ替えは上記で完成したので、次は入れ替える中身を作ります。

public class MockURLProtocol: URLProtocol {

    // 引数のURLRequestを処理できる場合はtrue
    override open class func canInit(with request:URLRequest) -> Bool {
        return true
    }

    // URLRequestの修正が必要でなければそのまま返す。
    override open class func canonicalRequest(for request: URLRequest) -> URLRequest {
        return request
    }

    // 通信開始時に呼ばれるメソッド、ここに通信のモックを実装します。
    override open func startLoading() {
        let delay: Double = 1.0 // 通信に1秒かかるモック
        DispatchQueue.global().asyncAfter(deadline: .now() + delay) {
            self.client?.urlProtocol(self, didLoad: response!) // 結果を返す
            self.client?.urlProtocolDidFinishLoading(self)     // 通信が終了したことを伝える

            // エラー時のハンドリングもこちらで可能です。
            // self.client?.urlProtocol(self, didFailWithError: error)
        }
    }

    // 通信停止時に呼ばれるメソッド
    override open func stopLoading() {
    }

    private var response: Data? {
        // URLなどでパターンマッチングすることで結果を切り替えることも出来る
        // self.request.url
        let json = "{\"mock\": \"data\"}"  // モック用のJSONデータ
        return json.data(using: .utf8)
    }
}

overrideが付いているメソッドは実装が必須のメソッドです。

特に重要なのはstartLoadingメソッドで、この中で、

client?.urlProtocol(self, didLoad: 返却するData)
client?.urlProtocolDidFinishLoading(self)

を呼び出すことで、独自のレスポンスを返すことができます。
(clientはURLProtocolが持っているURLProtocolClientというプロトコルです)

この例では決め打ちのJSONデータですが、URLProtocolはURLRequest(self.request)を持っているので、パターンマッチングでURLやパラメータを考慮して、JSONを出し分けることも簡単にできます。

完成

以上で完成です。

最初の例を実行してみると

// スタブに置き換え
URLSessionConfiguration. setupMockDefaultSessionConfiguration()

// Alamofireの場合
Alamofire.request("http://echo.jsontest.com/key/value/one/two").responseJSON { response in
    print(response.result.value!)
}

{
    mock = data;
}

このようにモックデータに差し替えができます。

最後に

このサンプルを使いやすくしたものをライブラリ化してみました。
気になった方はぜひ見てみてください!

https://github.com/tattn/Replacer

関連

Using NSURLProtocol for Testing
https://yahooeng.tumblr.com/post/141143817861/using-nsurlprotocol-for-testing


『 Swift 』Article List
Category List

Eye Catch Image
Read More

Androidに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

AWSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Bitcoinに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

CentOSに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

dockerに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

GitHubに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Goに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Javaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

JavaScriptに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Laravelに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Pythonに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Rubyに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Scalaに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Swiftに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Unityに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Vue.jsに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

Wordpressに関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。

Eye Catch Image
Read More

機械学習に関する現役のエンジニアのノウハウ・トレンドのトピックなど技術的な情報を提供しています。コード・プログラムの丁寧な解説をはじめ、初心者にもわかりやすいように写真や動画を多く使用しています。