如何关闭 XCTestCase 中的警报
How to dismiss Alert in XCTestCase
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: {[=10=].title == title}), let block = action.value(forKey: "handler") else {
return
}
let handler = unsafeBitCast(block as AnyObject, to: AlertHandler.self)
handler(action)
}
}
测试用例
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)
}
}
运行 个测试正在通过。但是当 ProfileTests
中的所有测试都是 运行
testAPIWhenIsFlagTrue:通过
testAPIWhenIsFlagFalse: 失败
"XCTAssertEqual failed: ("Optional(0)") is not equal to ("Optional(1)")"
调试结果
["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)")
观察:
)
两个测试的相同警报捕获。
加入计数增加到 4
函数中
tapButton()
我们正在调用警报操作处理程序,但并未解除实际警报。
如何解除 XCTestCase 中的警报?
注意:- showAlertOnWindow() 在 Utility class 中并且已被各种 classes 使用。
单元测试警报最安全的方法是让它们不实际出现。已记录但未显示警报:
- 警报不会干扰测试
- 测试不会互相干扰
为此,请尝试使用 https://github.com/jonreid/ViewControllerPresentationSpy。在测试代码中,实例化一个AlertVerifier
。然后调用
verify()
确认外观等属性
executeAction(forButton:)
调用特定按钮的闭包
无需关闭任何内容,因为 AlertVerifier
存在时不会显示真正的警报。
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: {[=10=].title == title}), let block = action.value(forKey: "handler") else {
return
}
let handler = unsafeBitCast(block as AnyObject, to: AlertHandler.self)
handler(action)
}
}
测试用例
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)
}
}
运行 个测试正在通过。但是当 ProfileTests
中的所有测试都是 运行
testAPIWhenIsFlagTrue:通过
testAPIWhenIsFlagFalse: 失败
"XCTAssertEqual failed: ("Optional(0)") is not equal to ("Optional(1)")"
调试结果
["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)")
观察:
两个测试的相同警报捕获。
加入计数增加到 4
函数中
tapButton()
我们正在调用警报操作处理程序,但并未解除实际警报。 如何解除 XCTestCase 中的警报? 注意:- showAlertOnWindow() 在 Utility class 中并且已被各种 classes 使用。
单元测试警报最安全的方法是让它们不实际出现。已记录但未显示警报:
- 警报不会干扰测试
- 测试不会互相干扰
为此,请尝试使用 https://github.com/jonreid/ViewControllerPresentationSpy。在测试代码中,实例化一个AlertVerifier
。然后调用
verify()
确认外观等属性executeAction(forButton:)
调用特定按钮的闭包
无需关闭任何内容,因为 AlertVerifier
存在时不会显示真正的警报。