Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
通信のスタブ化を自作した話 (iOS)
Search
Tatsuya Tanaka
January 14, 2017
Technology
1
1.3k
通信のスタブ化を自作した話 (iOS)
iOSでのユニットテスト用に通信をスタブ化する方法とその実装方法について。
@iOS_LT #25
Tatsuya Tanaka
January 14, 2017
Tweet
Share
More Decks by Tatsuya Tanaka
See All by Tatsuya Tanaka
iPhoneのセンサー情報をmacOSアプリでリアルタイム活用するための技術
tattn
1
650
Better use of SwiftUI
tattn
0
450
Swift Concurrencyによる安全で快適な非同期処理
tattn
2
1.3k
iOSアプリの技術選択2022
tattn
7
3.9k
Widget Suggestions 対応と ヤフーの新OS対応
tattn
1
1.3k
WidgetKitで良い体験を作るには / Good experience with WidgetKit
tattn
2
1.7k
既存アプリにSwiftUIをどう組み込んでいくか
tattn
8
2.4k
iOS 14からのアプリ内課金
tattn
5
2.9k
iOS 14の位置情報系アップデート
tattn
0
22k
Other Decks in Technology
See All in Technology
dbt開発 with Claude Codeのためのガードレール設計
10xinc
2
1.3k
S3アクセス制御の設計ポイント
tommy0124
3
200
Platform開発が先行する Platform Engineeringの違和感
kintotechdev
4
580
KotlinConf 2025_イベントレポート
sony
1
140
JTCにおける内製×スクラム開発への挑戦〜内製化率95%達成の舞台裏/JTC's challenge of in-house development with Scrum
aeonpeople
0
240
[ JAWS-UG 東京 CommunityBuilders Night #2 ]SlackとAmazon Q Developerで 運用効率化を模索する
sh_fk2
3
450
slog.Handlerのよくある実装ミス
sakiengineer
4
310
テストを軸にした生き残り術
kworkdev
PRO
0
210
Rustから学ぶ 非同期処理の仕組み
skanehira
1
140
バイブスに「型」を!Kent Beckに学ぶ、AI時代のテスト駆動開発
amixedcolor
2
570
2025年になってもまだMySQLが好き
yoku0825
8
4.8k
Practical Agentic AI in Software Engineering
uzyn
0
110
Featured
See All Featured
What's in a price? How to price your products and services
michaelherold
246
12k
Unsuck your backbone
ammeep
671
58k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
4 Signs Your Business is Dying
shpigford
184
22k
BBQ
matthewcrist
89
9.8k
KATA
mclloyd
32
14k
Docker and Python
trallard
46
3.6k
Agile that works and the tools we love
rasmusluckow
330
21k
The Cost Of JavaScript in 2023
addyosmani
53
8.9k
Raft: Consensus for Rubyists
vanstee
140
7.1k
Designing for Performance
lara
610
69k
Build The Right Thing And Hit Your Dates
maggiecrowley
37
2.9k
Transcript
通信の スタブ化 を自作した話 Presented by Tatsuya Tanaka tattn tattn tanakasan2525
通信部分のユニットテスト 実際に通信をしてテストすることも可能。 しかし、 スタブ化することで以下のような メリット がある。 無駄なサー バー 負荷を減らせる サー
バー が開発中・ 停止中でもテストできる 任意の通信エラー をテストできる 2
通信部分のスタブ化 iOS では3 つの方法がよく用いられている。 1. 依存性注入 (DI) 2. 継承 3.
ライブラリ (OHHTTPStubs, OCMock, Mockingjay) 3
1. 依存性注入 (DI) class APIClient { let requester: Requestable init(requester:
Requestable = RealRequester()) { self.requester = requester } ... } class RealRequester: Requestable {} // 実際の通信用 class MockRequester: Requestable {} // モック用 APIClient(requester: MockRequester()) // モックに差し替え 予めDI を意識した設計が必要 4
2. 継承 open class APIClient { open func request() {}
} class MockAPIClient: APIClient { override func request() {} // モックに上書き } let apiClient: APIClient = MockAPIClient() 継承できないstruct では不可 クラスごとにモック用のクラスを作るのが面倒 5
3. ライブラリ (OHHTTPStubs, OCMock, Mockingjay) OHHTTPStubs で、 レスポンスをJSON に差し替える場合 stub(isHost("mywebservice.com"))
{ _ in let stubPath = OHPathForFile("wsresponse.json", type(of: self)) return fixture(filePath: stubPath!, headers: ["Content-Type":"application/json"]) } 使いやすい 6
今回は3 のように扱える仕組みを実装 7
その方法とは... 8
Method Swizzling & URLProtocol 9
Method Swizzling メソッドの実装を差し替える手法。 Objective-C のランタイムを使用する。 紹介時間が足りないので 詳しくはアヒルの方の記事を見てね http://qiita.com/paming/items/25eaf89e4f448ab05752 注意: Pure
なSwift のクラスでは利用できない (Swift のリフレクションは読み込み専用のため) 10
URLProtocol サポー ト外のプロトコルで通信する場合や リクエストを独自の方法で処理する時に使うクラス。 継承して独自の処理を実装。 少なくとも4 つのメソッドをoverride が必要。 class func
canInit(with request: URLRequest) -> Bool class func canonicalRequest(for request: URLRequest) -> URLRequest func startLoading() func stopLoading() 11
class func canInit(with request: URLRequest) -> Bool 引数のリクエストが処理できるか否かで真偽値を返す func startLoading()
独自のリクエストの処理を実装する func stopLoading() リクエストのキャンセル時の処理を実装する 12
スタブ化できそうな気が してきますね 13
モック用URLProtocol の実装 public class MockURLProtocol: URLProtocol { override open class
func canInit(with request: URLRequest) -> Bool { return true } override open class func canonicalRequest(for request: URLRequest) -> URLRequest { return request } override open func startLoading() { // 次のスライドで実装 } override open func stopLoading() { } } 14
モックデー タを返す override open func startLoading() { let delay: Double
= 1.0 // 通信に1 秒かかるモック DispatchQueue.main.asyncAfter(deadline: .now() + delay) { let json: [String: Any] = ["mock": "data"] let data = try! JSONSerialization.data(withJSONObject: json, options: .prettyPrinted) self.client?.urlProtocol(self, didLoad: data!) self.client?.urlProtocolDidFinishLoading(self) } } client はURLProtocol が持っているURLProtocolClient プロトコル。 これを利用して結果を返す。 15
作ったプロトコルを使ってみる プロトコルを登録 URLProtocol.registerClass(MockURLProtocol.self) URLSessionCon guration にもプロトコルを登録 let configuration = URLSessionConfiguration.default
configuration.protocolClasses?.insert(MockURLProtocol.self, at: 0) 16
作ったプロトコルを使ってみる 作ったcon guration を利用して通信 let url = URL(string: " テストするAPI
のURL")! URLSession(configuration: configuration).dataTask(with: url) { data, _, _ in let json = try! JSONSerialization.jsonObject(with: data!, options: .allowFragments) print(json) }.resume() json にはモックデー タの["mock": "data"] が入っている。 ( 通信はしていないので" テストするAPI のURL" のまま動かしてもこの結果) 17
18
ですが 19
独自のプロトコルの作成 public class MockURLProtocol: URLProtocol { ... } プロトコルを登録 URLProtocol.registerClass(MockURLProtocol.self)
URLSessionCon guration にプロトコルを登録 let configuration = URLSessionConfiguration.default configuration.protocolClasses?.insert(MockURLProtocol.self, at: 0) con guration をURLSession に渡す URLSession(configuration: configuration) 20
面倒 21
そこで Method Swizzling 22
URLSessionCon guration の実装を置換 public extension URLSessionConfiguration { public class func
setupMock() { let `default` = class_getClassMethod(URLSessionConfiguration.self, #selector(getter: URLSessionConfiguration.default)) let swizzled = class_getClassMethod(URLSessionConfiguration.self, #selector(getter: URLSessionConfiguration.mock)) method_exchangeImplementations(`default`, swizzled) } private dynamic class var mock: URLSessionConfiguration { let configuration = self.mock configuration.protocolClasses?.insert(MockURLProtocol.self, at: 0) URLProtocol.registerClass(MockURLProtocol.self) return configuration } } 23
すると、 このようになります 24
// モックに置き換え URLSessionConfiguration.setupMock() let url = URL(string: " テストするAPI のURL")!
URLSession.shared.dataTask(with: url) { data, _, _ in let json = try! JSONSerialization.jsonObject(with: data!, options: .allowFragments) print(json) }.resume() It's シンプル ( URLSession.shared でも使えます) 25
もっと使いやすくしたい 26
ライブラリ化しました ( 宣伝) https://github.com/tattn/Mokei Method Chain でレスポンスを自由にカスタマイズ stub.url("example.com") .json(["test": "data"])
.delay(0.5) // wait for 500ms .statusCode(400) Bundle 内のJSON ファイルをスタブとして登録可能 stub.json(filename: "fixture") // fixture.json Quick/Alamo re でも使えます 27
車輪の再発明楽しい 28
参考 Alamo re, URLSession の通信処理をMethod Swizzling でスタブに置き 換える http://qiita.com/tattn/items/e7db12f84fa51b3631d2 Swift
における現実的なモック https://realm.io/jp/news/tryswift-veronica-ray-real-world-mocking- swift/ Using NSURLProtocol for Testing https://yahooeng.tumblr.com/post/141143817861/using- nsurlprotocol-for-testing 29