Search code examples
swiftiowebkit

Unit testing WKNavigationDelegate functions


I have a UIViewController that implements some WKNavigationDelegate functions, and I want to unit test the logic in these functions. Here's an example:

func webView(_ webView: WKWebView,
             decidePolicyFor navigationAction: WKNavigationAction,
             decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
    guard let url = navigationAction.request.url else {
        decisionHandler(.cancel)
        return
    }

    if url.absoluteString != "https://my-approved-url" {
        decisionHandler(.cancel)
        return
    }

    decisionHandler(.allow)
}

I'd like my unit test to make sure decisionHandler is called with the right WKNavigationActionPolicy based on the request.url of the WKNavigationAction.

I can't figure out how to test this function, however. Calling .load() on the webview does not trigger the delegate functions when I'm running my test project. I have also tried to call this function directly to test it, but it doesn't seem to be possible to instantiate a new WKNavigationAction of my own (.request is read-only).

What is the right way to unit test logic in WKNavigationDelegate functions?


Solution

  • The most straightforward way to test the delegate methods is to simply call them.

    The trick here is to pass the arguments that allow the unit tests to validate the behaviour, and for this you can use test doubles.

    For the particular case of the navigation policy delegate method, you can use a Fake, by subclassing WKNavigationAction, and pass an instance of that class as input argument to the delegate method:

    final class FakeNavigationAction: WKNavigationAction {
        let urlRequest: URLRequest
        
        var receivedPolicy: WKNavigationActionPolicy?
        
        override var request: URLRequest { urlRequest }
    
        init(urlRequest: URLRequest) {
            self.urlRequest = urlRequest
            super.init()
        }
        
        convenience init(url: URL) {
            self.init(urlRequest: URLRequest(url: url))
        }
        
        func decisionHandler(_ policy: WKNavigationActionPolicy) { self.receivedPolicy = policy }
    }
    

    Later on, in the unit test:

    // setup
    let testURL = URL(string: "https://my-approved-url")!
    let testAction = FakeNavigationAction(url: testURL)
    
    // act
    controller.webView(webView, decidePolicyFor: testAction, decisionHandler: testAction.decisionHandler)
    
    // assert
    XCTAssertEqual(testAction.receivedPolicy, b: .cancel)
    

    Another approach would be to swizzle the getter for request, since WKNavigationAction is an Objective-C class, however that's more of a hacky solution.