单元测试在iOS

时间:2017-03-18 08:58:05

标签: ios swift unit-testing

我试图编写单元测试以确保键盘和呈现的视图控制器被正确触发,但我得到的奇怪行为我不明白我认为这与如何 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))
            }
        }
    }
}

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

有什么想法?

3 个答案:

答案 0 :(得分:10)

感谢您提出这个问题,如果我没有碰到这个问题,我就不会考虑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可能会或可能不会与它有关。

另请注意,spec类不保留窗口实例。我认为这是不必要的,因为当它成为关键窗口时,UIApplication实例会保留它,UIApplication将不会被释放。

_ = sut.view

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

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

希望这会有所帮助;)

答案 1 :(得分:1)

我过去曾成功尝试过我的运动方式,因此很高兴看到这里也介绍了这种方式。有时似乎会导致测试套件的实际运行出现问题,因此我最近对此避开了。也许这是iOS测试运行器工作方式的变化。

我最后得到了一个片段,仅用于演示文稿,因此我可以测试交互,而不必担心实际需要呈现屏幕的情况。

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(...)

答案 2 :(得分:0)

您可以将应用的委托作为单身人士。 在这种情况下,您将能够访问应用程序的窗口。 E.g。

[AppDelegate sharedDelegate].window

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