简体   繁体   中英

Unit testing presented view controller and keyboard in iOS

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?

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

You can make your app's delegate to be a singleton. In this case you will be able to access the window of the application. Eg

[AppDelegate sharedDelegate].window

Another approach is to create in your tests a new window, make it keyAndVisible , assign to it a rootViewController and deal with it independently.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM