Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Writing a unit-test for a class extension in Swift

I'm attempting to write a unit test for a class extension in Swift. The class extension itself will present a UIAlert with a specified title and message as such:

extension UIViewController {

    func presentAlert(title: String, message : String) {
        let alertController = UIAlertController(title: title, message: message, preferredStyle: .Alert)
            alertController.addAction(UIAlertAction(title: "Close", style: UIAlertActionStyle.Default, handler: nil))

        UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(alertController, animated: true, completion: nil)

    }
}

I created a file for my unit test containing the code below:

import XCTest

class AlertTest: XCTestCase {

    func testAlert() {

        let alert = presentAlert("Presented Alert", "This is an Alert!")

    }

}

However, I keep getting an error of "Use of unresolved identifier 'presentAlert'". I tried adding public to my extension after consulting this SO thread:

public func presentAlert(title: String, message : String)

but still no luck. Anyone have some insight?

EDIT

Based on the answer by @hkgumbs, this is my current code for my alert extension:

import Foundation

protocol Presentable {}

extension UIViewController {

    public func presentAlert(title: String, message : String) {
        let alertController = UIAlertController(title: title, message: message, preferredStyle: .Alert)
            alertController.addAction(UIAlertAction(title: "Close", style: UIAlertActionStyle.Default, handler: nil))

        UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(alertController, animated: true, completion: nil)

    }
}

In the view controller, where I want to show the alert, this would still be the correct way to call my alert, correct?

self.presentAlert("Invalid URL", message: "Please try again")

Secondly, based on your comment, this is what I what I understand by calling Presentable on a dummy value, but it's incorrect as SomethingPresentable has no member PresentAlert. Where I am going wrong in my understanding?

func testAlert() {

    let app = XCUIApplication()

    struct SomethingPresentable: Presentable {}

    SomethingPresentable.presentAlert("Presented Alert", message: "This is an Alert!")

    XCTAssert(app.alerts["Presented Alert"].exists)
    app.alerts["Presented Alert"].tap();

}

EDIT 2 @hkgumbs, based on your newest comment, this is what I have for the extension:

import Foundation

protocol Presentable {}

extension Presentable {

    func presentAlert(title: String, message : String) {
        let alertController = UIAlertController(title: title, message: message, preferredStyle: .Alert)
            alertController.addAction(UIAlertAction(title: "Close", style: UIAlertActionStyle.Default, handler: nil))

        UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(alertController, animated: true, completion: nil)

    }
}

And this is how I'm trying to call it from my ViewController:

Presentable.presentAlert("Invalid URL", message: "Please try again")

However, I get an error of "Use of instance member presentAlert on type Self; did you mean to use a variable of type Self instead?"

Then, I'm guessing this is how the test would look?

func testAlert() {

    let app = XCUIApplication()

    struct SomethingPresentable: Presentable {}

    SomethingPresentable.presentAlert("Presented Alert", message: "This is an Alert!")

    XCTAssert(app.alerts["Presented Alert"].exists)
    app.alerts["Presented Alert"].tap();

}
like image 474
narner Avatar asked Aug 12 '16 19:08

narner


1 Answers

As @dan alluded to, you need to call it from an instance of the UIViewController. Usually you don't want to instantiate framework objects in your tests if you can avoid it, so here are some options to avoid that:

  1. Make presentAlert static so that you can just UIViewController.presentAlert
  2. Make presentAlert a free function (don't put it in the extension)
  3. Extend a protocol instead – I think this is the cleanest option

protocol Presentable {}

extension Presentable {
    func presentAlert(title: String, message : String) { /* ... */ }
}

Then, whenever you need it you can extension UIViewController: Presentable {}. And in your tests, you can just use a dummy class. The added benefit with this approach is that you can reuse that function on any type if needed, without globally exposing it when you don't.

Addendum

When we extend the protocol we are saying "anything that implements this protocol will get this method for free." The trick here is that this protocol is empty and, therefore, very easy to "implement."

extension YourViewController: Presentable {}
like image 95
Kofi Gumbs Avatar answered Oct 22 '22 23:10

Kofi Gumbs