Search code examples
swifttddxctestxcode12xcuitest

Could not load Main.Storyboard in XCUITest


I am try to make UITestCase for UIViewcontrollers, But when I load main storyboard in my QuizAppUITests, It could not identify from Bundle and it's gives below error

Could not find a storyboard named 'Main' in bundle NSBundle </Users/mac/Library/Developer/CoreSimulator/Devices/CC199C69-F398-4A7C-882E-BFD3E72B95D3/data/Containers/Bundle/Application/BCC3F88D-E08C-4491-8340-699EAF98AB28/QuizAppUITests-Runner.app> (loaded) (NSInvalidArgumentException)

enter image description here

I have added Main Storyboard in QuizAppUITests Target as well And below is my code for test

import XCTest
@testable import QuizApp

class QuizViewControllerUITests: XCTestCase {
    func makeSUT() -> QuizViewController {
        let storyboard = UIStoryboard(name: "Main", bundle: Bundle(for: type(of: self)))
        let sut = storyboard.instantiateViewController(withIdentifier: "QuizViewController") as! QuizViewController
        _ = sut.view
        return sut
    }
    
    func test_loadQuizViewController() {
        let sut = makeSUT()
        sut.headerQuestion = "Q1"
        XCTAssertEqual(sut.headerQuestion, "Q1")
    }

}

Is there any required to change in BuildSetting of QuizAppUITests Target?


Solution

  • None of this makes sense for a UI test. Instead, put your code into your unit test target, which is QuizAppTests.

    My book iOS Unit Testing by Example has a chapter called "Load View Controllers". Looking there for what it says about storyboard-based view controllers:

    • Don't include the storyboard in your test target. Put it only in your app target.
    • Then you can load the storyboard with UIStoryboard(name: "Main", bundle: nil)
    • There's a new way to instantiate the view controller that doesn't require force-casting. Instead of instantiateViewController(withIdentifier:), use instantiateViewController(identifier:) and assign it to an explicitly typed variable.

    Like this:

    let sut: QuizViewController = storyboard.instantiateViewController(
        identifier: "QuizViewController"
    )
    

    Since you use the name of the class as the identifier, we can even get rid of the string. This protects us from typos, at compile time:

    let sut: QuizViewController = storyboard.instantiateViewController(
        identifier: String(describing: QuizViewController.self)
    )
    

    Finally, instead of _ = sut.view, we can be more explicit about loading the view:

    sut.loadViewIfNeeded()
    

    This hooks up the outlet connections.

    Again, all this belongs in your unit test target QuizAppTests, not your UI test target QuizAppUITests.

    For your actual tests, don't just assign a property in your system under test and check that it was assigned. That doesn't prove anything. Instead, I'd focus on testing:

    • That outlets are not nil
    • That interacting with controls does what you want
    • That navigation works
    • That view appearance hasn't changed from an approved snapshot

    This can all be done with TDD.