Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit testing WKNavigationDelegate functions

Tags:

io

swift

webkit

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?

like image 292
Terje Avatar asked Mar 14 '18 12:03

Terje


1 Answers

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.

like image 158
Cristik Avatar answered Oct 18 '22 04:10

Cristik