Chapter 5: Changing Tests with Fixtures – Exercise Solution

In Chapter 5, we learned about the Fixture Extension technique.
Defining a .fixture() method to initialize our domain types in the unit tests with reasonable default values gave us two significant benefits compared to calling init directly. First, we insulated ourselves from having to update all the tests that use a type every time its init changes. Second, we gained the ability to write tests that are free from the noise produced by initialization parameters that are necessary but do not affect the behavior under test.

The exercise at the end of Chapter 5 asked to add a price property of type Double to the MenuItem model.

Let’s see the process step by step.

Step 1: Modify the production code

In Chapter 3, we learned that the compiler is as much part of the Test-Driven Development process as the tests are.
The idea of starting from a test, which is at the core of TDD, really means starting with a source of feedback to drive the change in the code we want to make.
The source of feedback can be a failing test but also a compiler error.
When making a change to the type-signature of our code, leveraging the compiler as the source of feedback is a way to get moving fast.

Let’s update MenuItem by adding the new property and see what the compiler has to say about it.

// MenuItem.swift
struct MenuItem {

    // ...
    let price: Double
}

If you try to build the production code with the keyboard shortcut Cmd B, you’ll see that the build fails.

Step 2: Make the production code compile

Adding a new property to MenuItem implicitly changed the init method that the compiler synthesizes for us.
It went from:

init(category: String, name: String, spicy: Bool)

to:

init(category: String, name: String, spicy: Bool, price: Double)

As such, all the calls to MenuItem.init are now failing to compile:

// AlbertosApp.swift
// ...

// In this first iteration, the menu is a hard-coded array
let menu = [
    MenuItem(category: "starters", name: "Caprese Salad", spicy: false),
      // Compiler says: Missing argument for parameter 'price' in call
    MenuItem(category: "starters", name: "Arancini Balls", spicy: false),
      // Compiler says: Missing argument for parameter 'price' in call
    //...
]

To make the production code compile, we need to update the MenuItem.init calls in the placeholder dummy menu.

// AlbertosApp.swift
// ...

// In this first iteration, the menu is a hard-coded array
let menu = [
    MenuItem(category: "starters", name: "Caprese Salad", spicy: false, price: 3.5),
    MenuItem(category: "starters", name: "Arancini Balls", spicy: false, price: 4.0),
    //...
]

The production code now builds successfully.
If you try to build the test target, too, using the Shift Cmd U keyboard shortcut, you’ll get one compilation error.

Step 3: Make the test code compile

In the same way as the MenuItem.init calls in the production code failed to compile because they missed the new price property, so do those in the test code.
The only difference is that, in the tests, we centralized all of the instantiation logic in our flexible fixture static method, so, regardless of how many times we create MenuItems in the tests, we only have one place to update now.

From:

// MenuItem+Fixture.swift
@testable import Albertos

extension MenuItem {

  static func fixture(
      category: String = "category",
      name: String = "name",
      spicy: Bool = false
  ) -> MenuItem {
      MenuItem(category: category, name: name, spicy: spicy)
          // Compiler says: Missing argument for parameter 'price' in call
  }
}

to:

// MenuItem+Fixture.swift
@testable import Albertos

extension MenuItem {

  static func fixture(
      category: String = "category",
      name: String = "name",
      spicy: Bool = false,
      price: Double = 1.0
  ) -> MenuItem {
      MenuItem(category: category, name: name, spicy: spicy, price: price)
  }
}

If you try to build the production and test code now, the operation will succeed.
The tests will also still pass, as you can verify by running them with the Cmd U keyboard shortcut.

Alongside what we discussed in Chapter 5 itself, this exercise should drive home the value of using the Fixture Extension technique in your tests.

In the production code, when we added the price property, we got eight compiler errors because we instantiated eight dummy MenuItem models in our placeholder menu.
In the tests, though, despite having a total of six MenuItem instances in use, we only got one compiler error.