SwiftyJD
SwiftyJD

Reputation: 5441

Unit test when the rootViewController presents a view?

I'm trying to unit test a function which presents a view outside a viewController:

public func presentInOwnWindow(animated: Bool, completion: (() -> Void)?) {
        let alertWindow = UIWindow(frame: UIScreen.main.bounds)
        alertWindow.rootViewController = UIViewController()
        alertWindow.windowLevel = UIWindowLevelAlert + 1;
        alertWindow.makeKeyAndVisible()
    alertWindow.rootViewController?.present(self, animated: animated, completion: completion)
    }

so far all I can think about how to unit test it is like this:

  func test_presentInOwnWindow () {

       let presented = sut.presentInOwnWindow(animated: true) {}

        XCTAssertNotNil(presented)
    }

I've tried passing a bool for the completion block :

completion: ((Bool) -> Void)

but since its invoking the completion for:

rootViewController?.present

I get the error:

Cannot convert value of type '((Bool) -> Void)?' to expected argument type '(() -> Void)?'

Any idea how to unit test the function properly?

Upvotes: 0

Views: 2974

Answers (2)

Jon Reid
Jon Reid

Reputation: 20980

Several options to try:

  1. Swizzle UIViewController's present(_, animated, completion) to capture what was presented. I do this for UIAlertControllers https://qualitycoding.org/testing-uialertcontroller/ but that's because UIAlertControllers are so common. It may not be worth the trouble for your custom window.
  2. Not sure if this will work. makeKeyAndVisible and present probably don't produce immediate effects. Instead, they schedule the work. So you can try pumping the run loop with RunLoop.current.run(until: Date()). This works for activating text fields, but I don't know if it will work for you.
  3. Similarly, try adding an XCTestExpectation and waitForExpectations with a small timeout. As I note in https://qualitycoding.org/asynchronous-tests/, don't do any assertions in your expectation handler. Instead, capture the information you want and fulfill the expectation. Then assert against what you've captured.
  4. Alter the code you wrote to make it easier to test. Replace the last line by calling a block saved in a property in the SUT. For testing, replace this block. The test block can invoke the completion handler, which gives you a way to test the handlers.
  5. Test this code manually to your satisfaction. Then mock around it, so your tests check that this method is called (but don't actually call it).

Upvotes: 2

Cristik
Cristik

Reputation: 32815

Unit testing code that has side effects can be incredibly hard, especially if the side effects involve hardware, like the device screen. For this reason UI components are not suitable for unit testing, since they usually involve GPU operations.

Now, if you really want to test the component, you can go via two routes:

  • you write snapshot tests
  • you extract all the business logic into dedicated classes and test those.

Conclusion: unit test the business code and let QA test the UI behaves as expected.

Upvotes: 0

Related Questions