I'm trying to write unit tests to ensure that the keyboard and presented view controllers are triggered properly, but I'm getting the strange behaviour I don't understand that I believe is related to how UIWindow
works. I'm using Quick and Nimble, but I've tested with vanilla XCTest
and get the same issues.
My Code:
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))
}
}
}
}
At first, I wasn't putting the view controller in a window, which prevented it from presenting other view controllers. Now I'm trying to put in in a window, but that isn't working either. When this test is run in isolation the window is always nil. When I run it with a bunch of other tests the window is not nil sometimes, but tests still fail. There was a brief period where the tests did pass, but I can't replicate it anymore for some reason.
Any ideas what's up?
To make your Unit Test load the UIViewController that has an interface XIB file is even easier. Create an instance on UIViewController that uses XIB file. Make sure though that the view controller and the XIB files have the same names. Call the loadViewIfNeeded() method to make the viewDidLoad() execute.
A unit test is a function you write that tests something about your app. A good unit test is small. It tests just one thing in isolation. For example, if your app adds up the total amount of time your user spent doing something, you might write a test to check if this total is correct.
A view controller acts as an intermediary between the views it manages and the data of your app. The methods and properties of the UIViewController class let you manage the visual presentation of your app. When you subclass UIViewController , you add any variables you need to manage your data in your subclass.
Unit tests of controller logic. Unit tests involve testing a part of an app in isolation from its infrastructure and dependencies. When unit testing controller logic, only the contents of a single action are tested, not the behavior of its dependencies or of the framework itself.
Thanks for opening this question, I wouldn't have thought about the UIWindow
issue if I hadn't bumped into this.
After fiddling a bit with your code I was able to make my test pass by doing this:
beforeEach {
let window = UIWindow(frame: UIScreen.main.bounds)
window.makeKeyAndVisible()
window.rootViewController = sut
_ = sut.view
}
Two things to note:
let window = UIWindow(frame: UIScreen.main.bounds)
In my tests the value of UIApplication.shared.keyWindow
is nil
. I'm not sure if that would be the case in yours as well, I prevent the unit tests from loading the UIAppDelegate
which may or may not have something to do with it.
Also note that the window instance is not retained by the spec class. I believe this is unnecessary because when it's made key window the UIApplication
instance holds it, and UIApplication
won't get released.
_ = sut.view
This line is what does the trick. Trying to access the view of the view controller results in it being actually added to the view hierarchy (more info here, which solves the warning one would otherwise get:
Warning: Attempt to present <BarViewController: 0x7f87a9c5def0>
on <FooViewController: 0x7f87a9d5e320> whose view is not in the window
hierarchy!
Hope this helps ;)
I've tried the approach my mokagio successfully in the past, so I'm happy to see that presented here as well. It seems to create problems with the actual running of the test suite sometimes, so I shied away from it recently. Perhaps that's a change in how the iOS test runner works.
I've ended up a snippet that just stubs out the presentation, so I can test the interaction without worrying about screens actually needing to be presented, eg.
class MockPresentingViewController: UIViewController {
var presentViewControllerTarget: UIViewController?
override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) {
presentViewControllerTarget = viewControllerToPresent
}
}
And then in my tests, I can:
// action
container.present(viewController, animated: true, completion: nil)
// expectation
expect(host.presentViewControllerTarget).to(...)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With