12

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?

Terje
  • 980
  • 9
  • 15
  • No, the problem with that as I mentioned is that I cannot set the .request attribute of a new WkNavigationAction(), so I am not able to test the logic that way. – Terje Mar 14 '18 at 12:53

1 Answers1

21

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.

Cristik
  • 30,989
  • 25
  • 91
  • 127
  • 2
    For me releasing a subclassed `WKNavigationAction` leads to a crash on iOS 15. Anybody has an idea how to circumvent it? – mbi Oct 27 '21 at 09:19
  • Solution for the crash: https://stackoverflow.com/a/69740146. – Cristik Oct 20 '22 at 05:12
  • When i try to use viewController?.webView(WKWebView(), decidePolicyFor: testAction, decisionHandler: testAction.decisionHandler) getting 'Cannot call value of non-function type 'WKWebView?'' error. Any idea why? Swift version 5 – smartsanja Nov 25 '22 at 10:00
  • @smartsanja no idea, without seeing the relevant code, you might want to ask a new question about it. – Cristik Dec 01 '22 at 06:17