【问题标题】:Unit testing presented view controller and keyboard in iOS单元测试在 iOS 中呈现视图控制器和键盘
【发布时间】:2017-03-18 08:58:04
【问题描述】:

我正在尝试编写单元测试以确保正确触发键盘和显示的视图控制器,但我得到了奇怪的行为,我不明白我认为这与 UIWindow 有效。我正在使用QuickNimble,但我已经用香草XCTest 进行了测试并得到了同样的问题。

我的代码:

import Quick
import Nimble

class TestSpec: QuickSpec {
    override func spec() {

        let sut = UIViewController()

        // The window is nil when this test is run in isolation
        UIApplication.shared.keyWindow?.rootViewController = sut

        // This does not work either
        let window = UIWindow(frame: UIScreen.main.bounds)
        window.rootViewController = sut
        window.makeKeyAndVisible()

        describe("ViewController") {

            it("presents a UIAlertController") {
                let alert = UIAlertController(title: "Test", message: "This is a test", preferredStyle: .alert)
                let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
                alert.addAction(okAction)

                sut.present(alert, animated: true, completion: nil)
                expect(sut.presentedViewController).toEventually(beAnInstanceOf(UIAlertController.self))
            }
        }
    }
}

起初,我没有将视图控制器放在窗口中,这会阻止它显示其他视图控制器。现在我正试图放入一个窗口,但这也不起作用。当此测试单独运行时,窗口始终为零。当我用一堆其他测试运行它时,有时窗口不是零,但测试仍然失败。测试确实通过了一小段时间,但由于某种原因我无法再复制它。

有什么想法吗?

【问题讨论】:

  • 在您运行测试时您的主机应用程序是否在运行?

标签: ios swift unit-testing


【解决方案1】:

感谢您提出这个问题,如果我没有碰到这个问题,我不会想到 UIWindow 问题。

在修改了您的代码之后,我能够通过以下操作使测试通过:

beforeEach {
  let window = UIWindow(frame: UIScreen.main.bounds)
  window.makeKeyAndVisible()
  window.rootViewController = sut
  _ = sut.view
}

需要注意的两点:

let window = UIWindow(frame: UIScreen.main.bounds)

在我的测试中,UIApplication.shared.keyWindow 的值为nil。我不确定你的情况是否也是如此,我 prevent the unit tests from loading the UIAppDelegate 这可能与它有关,也可能无关。

另请注意,规范类不保留窗口实例。我相信这是没有必要的,因为当它成为关键窗口时,UIApplication 实例会持有它,而UIApplication 不会被释放。

_ = sut.view

这条线的诀窍是什么。尝试访问视图控制器的视图会导致它实际上被添加到视图层次结构中(更多信息here,它解决了警告,否则会得到:

Warning: Attempt to present <BarViewController: 0x7f87a9c5def0>
on <FooViewController: 0x7f87a9d5e320> whose view is not in the window 
hierarchy!

希望这会有所帮助;)

【讨论】:

  • 感谢您的输入!我早就放弃了这个问题的答案。
  • 谢谢@mokagio!我正在使用 EarlGrey 并且在测试之间重新初始化时遇到了麻烦。将 UI 创建移到测试设置中修复了它。
  • 感谢您回答这个问题!我遇到了同样的问题,但是我在做(_ = sut.view),但没有创建 UIWindow 并将 rootviewcontroller 设置为 sut,这使得 sut.presentedviewcontroller 始终为零。所以创建一个 UIWindow 并分配 rootViewController 就可以了
【解决方案2】:

我过去成功地尝试过我的 mokagio 方法,所以我很高兴看到这里也展示了这种方法。有时它似乎会给测试套件的实际运行带来问题,所以我最近避开了它。也许这是 iOS 测试运行器工作方式的变化。

我已经完成了一个 sn-p,它只是将演示文稿截断,因此我可以测试交互而不必担心实际需要呈现的屏幕,例如。

class MockPresentingViewController: UIViewController {
  var presentViewControllerTarget: UIViewController?

  override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) {
    presentViewControllerTarget = viewControllerToPresent
  }
}

然后在我的测试中,我可以:

// action
container.present(viewController, animated: true, completion: nil)

// expectation
expect(host.presentViewControllerTarget).to(...)

【讨论】:

  • 可爱的手术方法来解决问题!干得好
【解决方案3】:

您可以将应用的委托设置为单例。 在这种情况下,您将能够访问应用程序的窗口。例如

[AppDelegate sharedDelegate].window

另一种方法是在您的测试中创建一个新窗口,将其设为keyAndVisible,为其分配rootViewController 并独立处理。

【讨论】:

    猜你喜欢
    • 2021-06-18
    • 2017-03-21
    • 1970-01-01
    • 1970-01-01
    • 2021-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多