Optimizing code for testability

Now that the project has a test target, it's time to start adding some tests to it. Before you add tests, you should determine what to test. Take some time to look at the app and the code and try to think of things to test. Assume that the app is finished and that the trivia questions are loaded from a server.

Some of the things you might have thought of to test are:

  • Making sure that we can display the data we load from the network
  • Testing that selecting the correct answer triggers the expected code
  • Testing that choosing a wrong answer triggers the expected code
  • Ensuring that the first question is displayed after we show the last one
  • Testing that the question index increments

If you came up with most of the tests on this list, good job. You've successfully identified a lot of good test cases. But how do you test these cases? The project has been made hard to test intentionally, but let's see what tests can be written without refactoring the app right away.

Remove the test class that Xcode has generated for you and create a new one called LoadQuestionsTest. Use the following bit of boilerplate code in this file's implementation as a starting point for the tests:

import XCTest
@testable import MovieTrivia

typealias JSON = [String: Any]

class LoadQuestionsTest: XCTestCase {
  override func setUp() {
    super.setUp()
  }

  func testLoadQuestions() {

  }
}

Note the @testable import MovieTrivia line at the top of the file. This line imports the entire app target so you can access it in your tests. Before you implement the test body for testLoadQuestions, it's wise to think about what this method should test. If you look at the code in the app target, the trivia questions are loaded in theĀ viewDidAppear(_:) method of LoadTriviaViewController. Once the questions are loaded, the app moves on to the next screen. An important detail is that the triviaJSON property on LoadTriviaViewController is set once the questions are loaded.

Based on this information, you could write a test that creates an instance of LoadTriviaViewController, makes it appear, so the questions will load, and then waits until triviaJSON has a value to verify that the questions were successfully loaded. Writing a test that fits this description would involve many moving parts, way more than you should be comfortable with. MovieTrivia uses storyboards, so to obtain an instance of LoadTriviaViewController, the storyboard would have to be involved. This means that any changes or mistakes in the user interface would cause the logic test that checks whether data is loadedĀ to fail. This is not desirable because this test should only verify whether it's possible to load data, not whether the user interface updates once the load completes.

This is a great moment to start refactoring some code and make it more testable. The first piece of code that should be revamped for testability is the question-loading code.