Passing launch arguments to your app

In order to switch the loading of questions from the network to a local file for testing, we can pass a launch argument. This launch argument is then read by the app so it can load questions from the JSON file like we did before in the unit tests, rather than attempting to load trivia questions from the server.

To prepare for the launch argument and loading the JSON file, create a folder named Shared in your project and move the TriviaQuestions.json file to it. Make sure you add it to the test target, the app target, and the UI test target. We won't need it in the UI test target just yet, but we will later, so you might just as well add it to the UI test target while you're at it.

In order to pass launch arguments to the app, we should modify the setUp() method in the UI test class:

override func setUp() {
    super.setUp()
    
    continueAfterFailure = false
    
    let app = XCUIApplication()
    app.launchArguments.append("isUITesting")
    app.launch()
}

The XCUIApplication instance that represents the app has a launchArguments property, which is an array of strings. You can add strings to this array prior to launching the app. We can then extract this launch argument in our app. Modify the loadTriviaQuestions(callback:) method in TriviaAPI.swift as shown in the following code snippet:

func loadTriviaQuestions(callback: @escaping QuestionsFetchedCallback) {
    if ProcessInfo.processInfo.arguments.contains("isUITesting") {
        loadQuestionsFromFile(callback: callback)
        return
    }
    
    // existing implementation...
}

The code highlighted in bold should be inserted above the existing implementation of this method. The snippet checks whether we're UI testing by reading the app's launch arguments. If the UI testing argument is present, we call the loadQuesionsFromFile(callback:) method to load the questions from the JSON file instead of loading it from the network.

Note that it's not ideal to perform checks such as the preceding one in your production code. It's often better to wrap configuration like this in a struct that can be modified easily. You can then use this struct throughout your app instead of directly accessing process info throughout your app. An example of such a configuration could look like this:

struct AppConfig {
    var isUITesting: Bool {
        ProcessInfo.processInfo.arguments.contains("isUITesting")
    }
}

We won't use this configuration class in this app since it's not needed for our small app. But for your own apps you might want to implement a configuration object regardless of app size since it leads to more maintainable code.

If you build the app right now, you should get a compiler error because loadQuesionsFromFile(callback:) is not implemented in the API class yet. Add the following implementation for this method:

func loadQuestionsFromFile(callback: @escaping QuestionsFetchedCallback) {
    guard let filename = Bundle.main.path(forResource: "TriviaQuestions", ofType: "json"),
        let triviaString = try? String(contentsOfFile: filename),
        let triviaData = triviaString.data(using: .utf8)
        else { return }
    
    callback(triviaData)
}

It's very similar to the question loading method we wrote for the unit tests; the only difference is that we're using a different way to obtain the bundle from which we're loading the questions.

If you run your UI tests now, they will fail. The reason for this is that, when the test framework starts looking for the elements we tapped before, they don't exist. This results in a test failure because we can't tap elements that don't exist.

We should adjust our tests a bit because tapping loaders are not the most useful UI test. It's a lot more useful for us to make sure that we can tap buttons and that the UI updates according to the result of tapping a button. To do this, we're going to write a UI test that waits for the question and buttons to appear, taps them, and checks whether the UI has updated accordingly. We'll also load the questions file so we can check that tapping a wrong or right answer works as intended.