使用 UIWindow() 进行单元测试时出现 NSInternalConsistencyException

Posted

技术标签:

【中文标题】使用 UIWindow() 进行单元测试时出现 NSInternalConsistencyException【英文标题】:NSInternalConsistencyException when Unit Testing with UIWindow() 【发布时间】:2016-07-13 23:43:51 【问题描述】:

我一直使用following post 作为指导,了解如何从与特定UIViewController 无关的代码中显示UIAlertController。现在,我想对这段代码进行单元测试:

func showAlert(alert: UIAlertController, animated: Bool, completion: (()->Void)?) 

    let alertWindow = UIWindow(frame: UIScreen.mainScreen().bounds)

    // Keep a strong refence to the window. 
    // We manually set this to nil when the alert is dismissed
    self.alertWindow = alertWindow

    alertWindow.rootViewController = UIViewController()        
    if let currentTopWindow = UIApplication.sharedApplication().windows.last 
        alertWindow.windowLevel = currentTopWindow.windowLevel + 1
    
    else 
        // This case only happens during unit testing
        Logger.trace(ICELogLevel.Error, category: .Utility, message: "The application doesn't have a window being displayed!")
    

    // preload the viewController for unit testing 
    // (see https://www.natashatherobot.com/ios-testing-view-controllers-swift/ )
    let _ = alertWindow.rootViewController?.view
    alertWindow.makeKeyAndVisible()

    alertWindow.rootViewController!.presentViewController(self.alertController, animated: animated, completion: completion)

但是,在运行单元测试时,在 alertWindow.makeKeyAndVisible() 行上,我得到一个 NSInternalInconsistencyException: props must have a valid clientID

此代码适用于应用程序代码,我不希望使用模拟 UIWindow 等,因为我希望验证警报 实际上 显示在 真实 UIWindow.

关于我们如何在单元测试中使用 UIWindows() 的任何指导?我做错了什么?

【问题讨论】:

这可能不是你想听到的,但在我看来你所做的总是错的。以这种方式将额外的 UIWindow 入侵到视图层次结构中是没有意义的。 — 但是,为了更有帮助:这真的是您想要进行单元测试的东西吗?这听起来更像是 UI 测试的候选者。 我不认为这是黑客行为。请参阅this post 了解添加 UIWindows 的意义所在。我认为这是其中之一——我们希望这个警报“浮动”在其他 UI 元素之上。此外,我的问题中链接的帖子表明这是 Apple 对 UIAlertController 问题的内部解决方案 Apple 的 自己的 警报(例如,来电)当然是窗口。但这在应用程序之外。这并不意味着 应该这样做,这也不是 UIAlertController 的工作方式。看,如果你愿意,我可以给你一个完全正常的非 hacky 非窗口方式来使自定义警报“浮动”在其他 UI 元素之上。但当然,这完全取决于你想走哪条路。 嗯,是的,我正在关注问题开始时链接的帖子。如果有更好的方法可以做到这一点,我会全力以赴 github.com/mattneub/custom-alert-view-iOS7 演示了正确的现代方式 - 浮动警报的位置、大小和内容完全取决于您。碰巧我试图让它看起来像 UIAlertController 的视图,但无论如何你都不需要这样做。 【参考方案1】:

问题在于makeKeyAndVisible 调用的代码在内部需要一个正在运行的 UIApplication 实例。这就是为什么在测试框架而不是在测试应用程序时抛出异常的原因。应用程序测试将测试包注入到正在运行的应用程序中。简单的解决方法是从:

alertWindow.makeKeyAndVisible()

alertWindow.isHidden = false

这可行,并允许一个视图控制器与一个窗口隔离地进行测试。缺点当然是这与makeKeyAndVisible 不同相同 - 它没有触发异常的副作用。

另一种策略是专门创建一个新的空应用程序目标来支持测试。将被测框架嵌入其中,并将其用作单元测试的主机应用程序。

【讨论】:

我有一个框架来测试这些与 UIKIt 相关的副作用是否已经发生,我目前正在尝试将框架嵌入到一个虚拟应用程序中。您以前是否尝试过这样做并且可以提供任何指导?【参考方案2】:

我们还没有解决 UIWindow 是否可以设置为在单元测试环境中工作的问题,但正如 @matt 的评论让我想到的那样,这可能不是我们首先需要进行单元测试的东西,原因如下:

    我们已经有了在警报场景中运行的 UI 测试。如果构建中断正在显示的警报,我们会很快注意到 对于其余的单元测试目的,我们可以简单地存根此方法以立即调用完成回调。 根据问题的最初动机,似乎没有办法在单元测试中正确使用 UIWindow 对象。

【讨论】:

以上是关于使用 UIWindow() 进行单元测试时出现 NSInternalConsistencyException的主要内容,如果未能解决你的问题,请参考以下文章

Play 2.2:使用 Play Caching (Scala) 对代码进行单元测试时出现问题

获取错误:在角度 js 中进行单元测试时出现 $injector:modulerr 模块错误

ReferenceError:未定义窗口。当我通过 jest 运行 npm test 进行单元测试时出现此错误

为啥在尝试对整个 Spring Batch Job 进行单元测试时出现此错误?没有可用的“org.springframework.batch.core.Job”类型的合格bean

使用 Roboelectric 3.8 运行单元测试时出现 NoClassDefFoundError

运行测试单元时出现 VUE 错误