0
votes

I learning TTD, and have problem with navigation controller in Unit-testing. When I'm trying to push Detail View Controller through navigation stack (pushViewController(ViewController, animated:) ) with my mock controller, in test push function not perform (its perform only first time, when navigationController initialise). On simulation iPhone, app work correctly. In code mockNavigationController have value pushedVC, that changes when pushViewController perform. When user tap on cell, dataProvider (delegate and dataSource for tableCell) post notification to ViewController (sut), that implements showDetails method.

I'd try take topViewController from navigationController: sut.navigationController?.topViewController - it's return sut ViewController. Try not initialise navigationController in test. sut.navigationController?.topViewController - it's return nil.

This beginning of XCTestCase

var sut: EatersListViewController!

    override func setUp() {
        super.setUp()

        let storyBoard = UIStoryboard(name: "Main", bundle: nil)
        let vc = storyBoard.instantiateViewController(withIdentifier: String(describing: EatersListViewController.self))
        sut = vc as? EatersListViewController

        sut.loadViewIfNeeded()
    }

This test function

func testSelectedRowPushedDetailVC() {
        let mockNavigationController = MockNavigationController(rootViewController: sut)
        UIApplication.shared.keyWindow?.rootViewController = mockNavigationController

        let eater1 = Eater(name: "Foo")
        sut.dataProvider.manager!.addEater(eater: eater1)

        sut.loadViewIfNeeded()

        sut.tableView.delegate?.tableView?(sut.tableView, didSelectRowAt: IndexPath(row: 0, section: 0))

        guard let detailEaterVC = mockNavigationController.pushedVC as? DetailEaterViewController else {
            XCTFail()
            return
        }
        detailEaterVC.loadViewIfNeeded()

        XCTAssertNotNil(detailEaterVC.eaterNameLabel)
        XCTAssertEqual(detailEaterVC.eaterData, eater1)
}

This function from ViewController

@objc func showDetails(withNotification notification: Notification) {
        guard
            let userInfo = notification.userInfo,
            let eater = userInfo["eater"] as? Eater,
            let detailEaterVC = storyboard?.instantiateViewController(withIdentifier: String(describing: DetailEaterViewController.self)) as? DetailEaterViewController else { return }
        detailEaterVC.eaterData = eater
        navigationController?.pushViewController(detailEaterVC, animated: true)
}

And MockNavigationController

extension EatersListViewControllerTests {
    class MockNavigationController: UINavigationController {

        var pushedVC: UIViewController?

        override func pushViewController(_ viewController: UIViewController, animated: Bool) {
            pushedVC = viewController
            super.pushViewController(viewController, animated: animated)
        }
    }
}

I'd expected that XCTAssert work correct, but every time test failed on XCTFail() line. I think somewhere error, and don't know here.

XCTAssertNotNil(detailEaterVC.eaterNameLabel)
XCTAssertEqual(detailEaterVC.eaterData, eater1)

Need help with code, where I wrong. Thanks for reading.

1

1 Answers

0
votes

Hey @Alexander, welcome to StackOverflow. 👋

You say that dataProvider is the .dataSource and .delegate for the UITableView in your view controller under test, and that it's the one responsible for starting the navigation.

Are you sure that in the tests dataProvider is actually set as the .dataSource and .delegate? If that's not the case then the code starting the navigation will never be called.

You could use breakpoints to verify two things:

  1. If your showDetails method is called
  2. If the pushViewController(_:, animated:) method in your MockNavigationController is called

I'm guessing one of them isn't called, and that might point you towards the cause of your issue.

If you allow me, a few extra words:

  • I would recommend using the NavigationDelegate pattern to test this behaviour. It would make it simpler for you, by removing the need to fiddle with UIApplication.
  • It's better to use _ = sut.view or sut.beginAppearanceTransition(true, animated: false) to trigger the setup of the view controller's view in the tests