jahoven
jahoven

Reputation: 139

XCTestCase: UIApplication.shared.keyWindow returns nil

When I call

UIApplication.shared.keyWindow

to try and set the root view controller in my test class, the key window returns nil. Why is this happening?

Here's how I set up my storyboard:

let testBoard = UIStoryboard(name: "TestStoryboard", bundle: Bundle(for: type(of: self)))
let vc = testBoard.instantiateViewController(withIdentifier: "TestController")

UIApplication.shared.keyWindow?.rootViewController = vc

_ = vc.view
vc.viewDidLoad()

Upvotes: 8

Views: 5128

Answers (2)

Chris Birch
Chris Birch

Reputation: 2041

I had a similar problem when testing UI inside a library that required access to a real UIWindow but had no test app of it's own.

My workaround was to add a dummy iOS single view target, then assign that as the unit tests host application.

This of course gives your test code a UIApplication singleton complete with delegate and window to play with.

import XCTest
import UIKit

class ExampleTestCase: XCTestCase {
    var rootViewController: UIViewController!

    override func setUp() {
        guard let mainWindow = UIApplication.shared.delegate?.window,
            let window = mainWindow else {
                fatalError("Unable to find window")
        }
        rootViewController = UIViewController()
        window.rootViewController = rootViewController

        setupView()

        window.makeKeyAndVisible()
        RunLoop.current.run(until: Date())
    }
    func setupView() {
        rootViewController.view.backgroundColor = .red
        let label = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
        label.text = "Hello!"
        label.textColor = .white
        label.font = UIFont.systemFont(ofSize: 25)
        //Do something with window
        rootViewController.view.addSubview(label)
        label.translatesAutoresizingMaskIntoConstraints = false
        label.centerXAnchor.constraint(equalTo: label.superview!.centerXAnchor).isActive = true
        label.centerYAnchor.constraint(equalTo: label.superview!.centerYAnchor).isActive = true
    }

    func testExample() {
        print("Im a useless test!")
    }
}

Setup host app

enter image description here

Upvotes: 0

InsertWittyName
InsertWittyName

Reputation: 3940

Create a window and assign the view controller.

let testBoard = UIStoryboard(name: "TestStoryboard", bundle: Bundle(for: type(of: self)))
let vc = testBoard.instantiateViewController(withIdentifier: "TestController")

let window = UIWindow()
window.rootViewController = vc
window.makeKeyAndVisible()

vc.view.layoutIfNeeded()

// Add test here

I notice after that you're also calling vc.view and viewDidLoad. I'd recommend just accessing the view to get it to load and not calling viewDidLoad implicitely - personally I use vc.view.layoutIfNeeded()

Depending on what you actually need to test, for me it's very rare to have to assign the view controller to the window itself. You can normally get away with just creating an instance of the view controller, and if you're testing any of the UI code also ensuring the view is populated.

One of the only times I've needed to assign the view controller to a window is when testing things like navigation, where I want to assert that another view controller is being presented modally due to some action.

Upvotes: 11

Related Questions