关于这个主题有几个现有的问题,但它们并不是我所追求的。我为我的应用写了一个小的Swift应用评级提示,它提示了两个UIAlertController
实例,一个由另一个触发。
我现在正试图对此进行单元测试,并尝试在测试中达到第二个警报。我已经写了一个简单的间谍来检查第一个控制器,但我想要一种方法来触发第一个警报上的一个动作,然后再显示第二个动作。
我已经尝试了alert.actions.first?.accessibilityActivate()
,但它似乎并没有打破这一行动的处理程序 - 这就是我所追求的目标。
答案 0 :(得分:4)
以下是我的所作所为:
创建了我的类的模拟版本,它将显示警报控制器,在我的单元测试中,使用了这个模拟。
覆盖我在非模拟版本中创建的以下方法:
func alertActionWithTitle(title: String?, style: UIAlertActionStyle, handler: Handler) -> UIAlertAction
在重写的实现中,存储了一些属性中的操作的所有详细信息(Handler
只是一个类型() -> (UIAlertAction)
)
var didCreateAlert = false
var createdTitles: [String?] = []
var createdStyles: [UIAlertActionStyle?] = []
var createdHandlers: [Handler?] = []
var createdActions: [UIAlertAction?] = []
然后,在运行我的测试时,为了遍历警报的路径,我实现了一个callHandlerAtIndex
方法来迭代我的处理程序并执行正确的。
这意味着我的测试看起来像这样:
feedback.start()
feedback.callHandlerAtIndex(1) // First alert, second action
feedback.callHandlerAtIndex(2) // Second alert, third action
XCTAssertTrue(mockMailer.didCallMail)
答案 1 :(得分:4)
不涉及更改生产代码以允许在单元测试中以编程方式轻敲UIAlertActions的解决方案,我发现in this SO answer。
在谷歌搜索答案时,将其张贴在这里以及这个问题对我来说是突然出现的,以下解决方案使我有更多的时间来查找。
将扩展名放在测试目标的下方:
extension UIAlertController {
typealias AlertHandler = @convention(block) (UIAlertAction) -> Void
func tapButton(atIndex index: Int) {
guard let block = actions[index].value(forKey: "handler") else { return }
let handler = unsafeBitCast(block as AnyObject, to: AlertHandler.self)
handler(actions[index])
}
}
答案 2 :(得分:0)
我使用上述卢克的指导来创建UIAlertAction
的子类,该子类保存其完成块,以便可以在测试期间调用它:
class BSAlertAction: UIAlertAction {
var completionHandler: ((UIAlertAction) -> Swift.Void)?
class func handlerSavingAlertAction(title: String?,
style: UIAlertActionStyle,
completionHandler: @escaping ((UIAlertAction) -> Swift.Void)) -> BoseAlertAction {
let alertAction = self.init(title: title, style: style, handler: completionHandler)
alertAction.completionHandler = completionHandler
return alertAction
}
}
如果愿意,可以自定义此选项以保存更多信息(例如标题和样式)。这是XCTest的示例,然后使用此实现:
func testThatMyMethodGetsCalled() {
if let alert = self.viewController?.presentedViewController as? UIAlertController,
let action = alert.actions[0] as? BSAlertAction,
let handler = action.completionHandler {
handler(action)
let calledMyMethod = self.presenter?.callTrace.contains(.myMethod) ?? false
XCTAssertTrue(calledMyMethod)
} else {
XCTFail("Got wrong kind of alert when verifying that my method got called“)
}
}
答案 3 :(得分:0)
我根据测试UIContextualAction
所采取的策略采用了略有不同的方法,该方法与UIAction
非常相似,但是将其handler
作为属性公开(不确定为什么苹果不会这样做)。对UIAction
做过同样的事情。我将警报动作提供程序(由协议封装)注入了我的视图控制器。在生产代码中,前者只是出售操作。在单元测试中,我使用了此提供程序的子类,该子类将操作和处理程序存储在两个字典中,可以在查询中查询它们然后将其触发。
typealias UIAlertActionHandler = (UIAlertAction) -> Void
protocol UIAlertActionProviderType {
func makeAlertAction(type: UIAlertActionProvider.ActionTitle, handler: UIAlertActionHandler?) -> UIAlertAction
}
具体对象(输入了标题以便以后检索)
class UIAlertActionProvider: UIAlertActionProviderType {
enum ActionTitle: String {
case proceed = "Proceed"
case cancel = "Cancel"
}
func makeAlertAction(title: ActionTitle, handler: UIAlertActionHandler?) -> UIAlertAction {
let style: UIAlertAction.Style
switch title {
case .proceed: style = .destructive
case .cancel: style = .cancel
}
return UIAlertAction(title: title.rawValue, style: style, handler: handler)
}
}
单元测试子类(存储以ActionTitle
枚举为键的操作和处理程序):
class MockUIAlertActionProvider: UIAlertActionProvider {
var handlers: [ActionTitle: UIAlertActionHandler] = [:]
var actions: [ActionTitle: UIAlertAction] = [:]
override func makeAlertAction(title: ActionTitle, handler: UIAlertActionHandler?) -> UIAlertAction {
handlers[title] = handler
let action = super.makeAlertAction(title: title, handler: handler)
actions[title] = action
return action
}
}
扩展UIAlertAction
以便在测试中启用键入的操作标题查找:
extension UIAlertAction {
var typedTitle: UIAlertActionProvider.ActionTitle? {
guard let title = title else { return nil }
return UIAlertActionProvider.ActionTitle(rawValue: title)
}
}
演示用法的示例测试:
func testDeleteHandlerActionSideEffectTakesPlace() throws {
let alertActionProvider = MockUIAlertActionProvider()
let sut = MyViewController(alertActionProvider: alertActionProvider)
// Do whatever you need to do to get alert presented, then retrieve action and handler
let action = try XCTUnwrap(alertActionProvider.actions[.proceed])
let handler = try XCTUnwrap(alertActionProvider.handlers[.proceed])
handler(action)
// Assert whatever side effects are triggered in your code by triggering handler
}