こんにちは、モバイルチームの@el_metal_です。
弊社のiOSアプリではWKWebView
でも認証が必要なコンテンツを表示しているため、WKWebView
で認証処理を行なっています。
今回はWKWebView
での認証ハンドリングをテストする方法についてご紹介します。
WKWebViewでの認証について
主な流れはHandling an Authentication Challengeに記載されています。
WKWebView
の認証ハンドリングはWKNavigationDelegate
のwebView(_:didReceive:completionHandler:)
が担っているので、これを呼んだ結果を検証すればテストできます。
ここでは、例えばクライアント証明書を使った通信が対象になります。
WKWebView
でのクライアント証明書を使った通信の実装に興味がある方は、こちらも併せてご覧ください。
前準備
webView(_:didReceive:completionHandler:)
を呼ぶにあたって引数の設定が必要です。
WebView
第一引数には通信をしたいWebView
を渡します。
URLAuthenticationChallenge
第二引数はURLAuthenticationChallenge
です。
このオブジェクトを新たに作るinit
は一つだけです。
URLProtectionSpace
同一の認証情報が適用される範囲のことで、一般にrealmと呼ばれます。
let protectionSpace = URLProtectionSpace(host: "cybozu.co.jp", port: 0, protocol: "https", realm: nil, authenticationMethod: NSURLAuthenticationMethodClientCertificate)
URLCredential
クレデンシャル情報と、利用する場合は永続化に用いるストレージのセットです。
URLAuthenticationChallengeSender
Authentication challengeを開始したオブジェクトです。
テストでは以下のようにURLProtocol
を用意して渡すことができます。
fileprivate class URLProtocolMock: URLProtocol, URLAuthenticationChallengeSender { // MARK: URLAuthenticationChallengeSender func use(_ credential: URLCredential, for challenge: URLAuthenticationChallenge) {} func continueWithoutCredential(for challenge: URLAuthenticationChallenge) {} func cancel(_ challenge: URLAuthenticationChallenge) {} // MARK: URLProtocol override class func canInit(with request: URLRequest) -> Bool { true } override open class func canInit(with task: URLSessionTask) -> Bool { true } override func startLoading() {} override func stopLoading() {} open override class func canonicalRequest(for request: URLRequest) -> URLRequest { request } }
completionHandler
レスポンスと共に実行されるクロージャです。 テストコードでは、ここでアサーションを行うと良いでしょう。
サンプルコード
WKWebView
を持つViewController
をWKNavigationDelegate
に設定し、テスト対象とします。
final class ViewController: UIViewController { @IBOutlet private var stackView: UIStackView! private var browser: WKWebView! override func viewDidLoad() { super.viewDidLoad() self.browser = WKWebView(frame: .zero) self.stackView.addArrangedSubview(browser) self.browser.navigationDelegate = self self.browser.uiDelegate = self self.browser.load(URLRequest(url: URL(string: "https://cybozu.co.jp")!)) } } extension ViewController: WKNavigationDelegate { func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { if challenge.previousFailureCount > 3 { completionHandler(.performDefaultHandling, nil) return } } }
テストでは、以下のようにViewController
を作って実行します。
final class WKNavigationDelegateTests: XCTestCase { func test() throws { let sut = UIStoryboard(name: "ViewController", bundle: nil).instantiateInitialViewController() as! ViewController sut.view.layoutIfNeeded() URLProtocol.registerClass(URLProtocolMock.self) let webView = sut.view.subviews .compactMap { $0 as? UIStackView } .first? .subviews .compactMap { $0 as? WKWebView } .first! let protectionSpace = URLProtectionSpace(host: "example.com", port: 0, protocol: "https", realm: nil, authenticationMethod: NSURLAuthenticationMethodClientCertificate) let credential = URLCredential() let challenge = URLAuthenticationChallenge(protectionSpace: protectionSpace, proposedCredential: credential, previousFailureCount: 1, failureResponse: nil, error: nil, sender: URLProtocolMock()) sut.webView(webView!, didReceive: challenge, completionHandler: { authChallengeDisposition, _ -> Void in XCTAssertEqual(authChallengeDisposition, .performDefaultHandling) }) } }
以上でWKWebView
の認証ハンドリングをテストすることができました。
おわりに
WKWebView
の認証ハンドリングは複雑になりやすい割にドキュメントに乏しく、テストを書くのに手間取りました。
本記事がお役に立てば幸いです。