How to dismiss Alert in XCTestCase

482 views Asked by At
class ProfileVC: BaseUIVC {
    var dispatchGroupHelper: DispatchGroupImpl
    var isFlag: Bool
    init(dispatchGroupHelper: DispatchGroupImpl = DispatchGroupHelper(), isFlag: Bool = false) {
        self.dispatchGroupHelper = dispatchGroupHelper
        self.isFlag = isFlag
        super.init(nibName: nil, bundle: currentBundle())
    }

    func save() {
        print("Outside isFlag : \(isFlag)")
        print("Outside self : \(self)")
        if let info = getData() {             
            showAlertOnWindow(message: "message") { (_) in
                print("Inside isFlag : \(self.isFlag)")
                print("Inside self : \(self)")
                if param.count > 0 {
                   self.dispatchGroupHelper.joinDispatchGroup()
                   self.requestAPI1(data: param)
                }
                if isFlag {
                    self.dispatchGroupHelper.joinDispatchGroup()
                    self.requestAPI2(data: param)
                }
                self.dispatchGroupHelper.notifyDispatchGroup(execute: {
                    self.handleResponse()
                })
            }
        }
    }

    public func showAlertOnWindow(title: String? = "", message: String? = nil,
                                  completionHandler: ((_ title: String) -> Void)? = nil) {
        let alert = UIAlertController(title: title ?? "", message: message,
                                      preferredStyle: UIAlertControllerStyle.alert)
        alert.addAction(UIAlertAction(title: "dummy", style: UIAlertActionStyle.default, handler: { (_) in
            if let completionHanlder = completionHandler {
                completionHanlder("OK")
            }
        }))
        alert.show()
        print("Display alert : \(alert)")
    }
}
class DispatchGroupHelper: DispatchGroupImpl {
    lazy var group: DispatchGroup? = {
        return DispatchGroup()
    }()
}
protocol DispatchGroupImpl {
    var group: DispatchGroup? { get }
    func joinDispatchGroup()
    func leaveDispatchGroup()
    func notifyDispatchGroup(execute: @escaping () -> Void)
}

extension DispatchGroupImpl {
    func joinDispatchGroup() {
        group?.enter()
    }

    func leaveDispatchGroup() {
        group?.leave()
    }

    func notifyDispatchGroup(execute: @escaping () -> Void) {
        group?.notify(queue: DispatchQueue.main, execute: execute)
    }
}

extension UIViewController {
    func show() {
        present(animated: true, completion: nil)
    }

    func present(animated: Bool, completion: (() -> Void)?) {
        if let rootVC = UIApplication.shared.keyWindow?.rootViewController {
            if #available(iOS 13.0, *), let navVC = rootVC as? UINavigationController,
                let visibleVC = navVC.visibleViewController {
                print("VisibleVC : \(visibleVC)")
                visibleVC.showPrompt(withAlertController: self)
            } else {
                rootVC.showPrompt(withAlertController: self)
            }
        }
    }
}

public extension UIAlertController {
        func tapButton(title: String) {
            guard let action = actions.first(where: {$0.title == title}), let block = action.value(forKey: "handler") else {
            return
        }
        let handler = unsafeBitCast(block as AnyObject, to: AlertHandler.self)
        handler(action)
    }
}

Test Case

class MockDispatchGroupImpl: DispatchGroupImpl {
    let group: DispatchGroup?
    var joinCount = 0
    var leaveCount = 0
    var isNotifyCalled = false
    init(group: DispatchGroup) {
        self.group = group
    }
    func joinDispatchGroup() {
        joinCount += 1
        print("Join: \(joinCount)")
    }
    func leaveDispatchGroup() {
        leaveCount += 1
        print("Leave")
    }
    func notifyDispatchGroup() {
        isNotifyCalled = true
        print("Notify")
    }
}

class ProfileTests: XCTestCase {
    func testAPIWhenIsFlagTrue() {
        var mockDispatchGroupHelper: MockDispatchGroupImpl? = MockDispatchGroupImpl(group: DispatchGroup())
        var profilerVC = getProfileVC(dispatchGroupHelper: mockDispatchGroupHelper!, isFlag: true)
        profilerVC?.save()
        let alert = UIApplication.shared.keyWindow?.rootViewController?.presentedViewController as! UIAlertController
        alert.tapButton(title: "OK")
        print("Capture Alert : \(alert)")
        print("TAP")
        XCTAssertEqual(mockDispatchGroupHelper?.joinCount, 2)
    }

    func testAPIWhenIsFlagFalse() {
        var mockDispatchGroupHelper: MockDispatchGroupImpl? = MockDispatchGroupImpl(group: DispatchGroup())
        var profilerVC = getProfileVC(dispatchGroupHelper: mockDispatchGroupHelper!, isFlag: false)
        profilerVC?.save()
        let alert = UIApplication.shared.keyWindow?.rootViewController?.presentedViewController as! UIAlertController
        alert.tapButton(title: "OK")
        print("Capture Alert : \(alert)")
        print("TAP")
        XCTAssertEqual(mockDispatchGroupHelper?.joinCount, 1)
    }
}

Running tests individually are passing. But when all tests are run in ProfileTests
testAPIWhenIsFlagTrue: PASS
testAPIWhenIsFlagFalse: FAIL

"XCTAssertEqual failed: ("Optional(0)") is not equal to ("Optional(1)")"

Debug Result

["testAPIWhenIsFlagTrue"]
Outside isFlag : true
Outside self: <ProfileVC: 0x7fdd13818600>
VisibleVC : <SplashViewController: 0x7fdd123023d0>
**Display alert : <UIAlertController: 0x7fdd1282a000>**    
**["Capture Alert : <UIAlertController: 0x7fdd1282a000>"]**
["TAP"]
Inside isFlag : true
Inside self: <ProfileVC: 0x7fdd13818600>
["Join: 1"]
["Join: 2"]
["Notify"]

["testAPIWhenIsFlagFalse"]
Outside isFlag : false
Outside self: <ProfileVC: 0x7fdd10242a00>
**VisibleVC : <UIAlertController: 0x7fdd1282a000>)**
Display alert : <UIAlertController: 0x7fdd1282a600>
**["Capture Alert : <UIAlertController: 0x7fdd1282a000>"]**
["TAP"]
Inside isFlag : true
Inside self: <ProfileVC: 0x7fdd13818600>
["Join: 3"]
["Join: 4"]
["Notify"]
XCTAssertEqual failed: ("Optional(0)") is not equal to ("Optional(1)")

Observation:
<UIAlertController: 0x7fdd1282a000>)
Same alert capture for both tests .
Join Count increasing to 4

In function

tapButton()

we are calling the alert action handler, but not dismissing the actual alert. How to dismiss alert in XCTestCase? Note:- showAlertOnWindow() is in Utility class and has been used by various classes.

1

There are 1 answers

0
Jon Reid On BEST ANSWER

The safest way to unit test alerts is to have them not actually appear. With the alert recorded but not shown:

  • The alerts won't interfere with tests
  • Tests won't interfere with each other

To that end, try using https://github.com/jonreid/ViewControllerPresentationSpy. In test code, instantiate an AlertVerifier. Then call

  • verify() to confirm its appearance and other attributes
  • executeAction(forButton:) to call the closure for a particular button

There's no need to dismiss anything, because a real alert isn't presented when an AlertVerifier exists.