Unit testing WKNavigationDelegate functions swift
Your second approach of directly calling the delegate method is the better approach as you are not relying on any behavior of WebKit in your unit tests.
The problem of not being able to instantiate a WKNavigationAction can be solved by subclassing. Create a MockNavigationAction
which returns the desired request you need for testing like so:
final class MockNavigationAction: WKNavigationAction {
var mockedRequest: URLRequest!
override var request: URLRequest {
return mockedRequest
}
And then in your unit test call the delegate method directly:
func test_AllowsCorrectURL() {
let sut = ViewController()
let action = MockNavigationAction()
action.mockedRequest = URLRequest(url: URL(string: "https://my-approved-url")!)
let allowExpectation = expectation(description: "Allows action")
sut.webView(WKWebView(), decidePolicyFor: action) { policy in
XCTAssertEqual(policy, .allow)
allowExpectation.fulfill()
}
waitForExpectations(timeout: 1.0)
}
Unit testing WKNavigationDelegate functions
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.
How to write unit test cases (XCTest) in UIWebView / WebKit - Swift
A good way is to create fake navigation actions to call manually the delegate.
In this question you have a good example to write test cases of this way. unit-testing-wknavigationdelegate-functions
Example to test loading in navigation:
// setup
let fakeNavigation = WKNavigation()
delegateObject.refresh() // Set loading to true and init the web view
XCTAssertTrue(delegateObject.loading)
delegateObject.webView(webView, didFinish: fakeNavigation)
XCTAssertFalse(delegateObject.loading)
Example to test the policy:
class FakeNavigationAction: WKNavigationAction {
let testRequest: URLRequest
override var request: URLRequest {
return testRequest
}
init(testRequest: URLRequest) {
self.testRequest = testRequest
super.init()
}
}
// setup
var receivedPolicy: WKNavigationActionPolicy?
let fakeAction = FakeNavigationAction(testRequest: ...)
// act
delegateObject.webView(webView, decidePolicyFor: fakeAction, decisionHandler: {
receivedPolicy = $0
})
XCTAssertEqual(receivedPolicy, theExpectedValue)
How to use didFinish navigation on an WKWebView passed to another class
Make it an instance var to retain the object so delegate to be called
var second = Second() /// << here
override func viewDidLoad(){
super.viewDidLoad()
......
}
func test(){
self.web!.navigationDelegate = self
self.web!.load(URLRequest(url: URL(string: "https://google.com")!))
}
How can we write Unit Test Case for the function in swift ios
Refactor the DownloadBaseViewController
in your app so you can mock the dependency:
extension DownloadBaseViewController:EMPDecisionTreeCoordinatorDelegate {
// Add this variable in DownloadBaseViewController
lazy var presentingController: ViewControllerPresenting? = self.decesiontreeCoordinator?.rootViewController
func decisionEmptyTreeFeedbackButtonTapped() {
if let feedbackNavVc = storyboard?.instantiateViewController(identifier: "PremiumFeedbackNavViewController") as? PremiumCustomNavigationController {
if let feedbackVc = feedbackNavVc.children.first as? PremiumFeedbackViewController {
feedbackVc.id = self.fileDetails?.id
self.presentingController?.present(feedbackNavVc, animated: true, completion: nil)
}
}
}
}
// You need this to mock the foreign dependency on UIViewController
protocol ViewControllerPresenting: AnyObject {
func present(_ viewControllerToPresent: UIViewController,
animated flag: Bool,
completion: (() -> Void)?)
}
extension UIViewController: ViewControllerPresenting {}
In the tests you inject a Spy object that will help you validate the correct behaviour:
final class UIViewControllerSpy: ViewControllerPresenting {
var viewControllerToPresent: UIViewController!
func present(_ viewControllerToPresent: UIViewController,
animated flag: Bool,
completion: (() -> Void)? = nil) {
self.viewControllerToPresent = viewControllerToPresent
}
}
class DownloadBaseViewControllerTests: XCTestCase {
var downloadBaseViewController: DownloadBaseViewController! = DownloadBaseViewController()
func testDecisionEmptyTreeFeedbackButtonTapped() throws {
// Given
let spyController = UIViewControllerSpy()
downloadBaseViewController.presentingController = spyController
// When
downloadBaseViewController.decisionEmptyTreeFeedbackButtonTapped()
// Then
let presentedController = spyController.viewControllerToPresent as? PremiumFeedbackViewController
XCTAssertNotNil(presentedController, "Download base view controller contains feedback view controller and succesfully able to navigate")
}
}
WKNavigationDelegate methods not called
It looks like you are adding private
with WKWebViewDelegate
Methods, Remove those like this and it will work.
extension ViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
debugPrint("didCommit")
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
debugPrint("didFinish")
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
debugPrint("didFail")
}
}
Try this out, Also don't forgot to load URL in WebView or whatever you want to load.
Related Topics
"Message from Debugger: Unable to Attach" When Running Tests on Osx App
Same Class Extension in Two Different Modules
How to Use Unsafemutablepointer<Opaquepointer> in Swift
Swift Spritekit I Detect a Collison But It Reads the Collision Mulitple Times
Swift Subclasses Used in Generics Don't Get Called When Inheriting from Nsobject
Response Struct Does Not Like Codingkeys
Opening Import File for Module 'Swift': Not a Directory
Navigationlink Is Grayed Out and Does Not Perform Any Action
How to Access Modifiers of a View in Swiftui
Swift - Kvo - Change.Newvalue and Change.Oldvalue Are Nil
Get a List of Nodes in an Specific Area
Why Doesn't Swift Force My Designated Initializer to Call Super
How to Return a Value from a Void Closure in Swift
How, Exactly, Do I Render Metal on a Background Thread