我试图编写单元测试以确保键盘和呈现的视图控制器被正确触发,但我得到的奇怪行为我不明白我认为这与如何 UIWindow
有效。我使用的是Quick和Nimble,但我已经使用香草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))
}
}
}
}
起初,我没有将视图控制器放在一个窗口中,这阻止了它呈现其他视图控制器。现在我试图进入一个窗口,但那也不起作用。当此测试单独运行时,窗口始终为零。当我用一堆其他测试运行它时,窗口有时不是零,但测试仍然失败。测试确实经过了短暂的一段时间,但由于某种原因我无法复制它。
有什么想法?
答案 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
并单独处理。