Part 1: Bootstrapping – Setting Up the Project, Core Data, and Unit Tests

Swift is still pretty new. Since it’s so new, it’s likely you haven’t had a chance to use it seriously. After all, investing in a hip new language is pretty risky: it takes time and might not lead you anywhere.

With Swift, it’s a safe bet to stay a Cocoa developer in the future. Apple pushes the language forward, so there’s a lot of momentum already. Swift is not in the same situation Java was in during the late 1990’s.

Initially, you may not know if errors happen because you can’t use Swift properly, or if it’s an actual implementation problem. I guess having existing code to port makes it easier to take Swift for a test drive. Without that luxury, you’ll have to learn faster.

That’s why we’re going to start with a natural approach to learning Swift and getting an application up and running.

This part of the book is not about designing and crafting the application in a “clean” manner. There’s no up-front design. This part is about understanding the new ecosystem of Swift and how it integrates with Xcode and existing Cocoa frameworks. It’s an exploratory part: I think you’ll be able to follow along with the resistance I encountered and how I thought and coded my way out of dead-ends better in this style.

I dedicate this part to making the switch to Swift and getting everything to run, including:

  1. Get Core Data and related tests running
  2. Prepare the user interface in its own layer
  3. Have tests in place for the app’s domain

In the second part, I’ll cover the actual application’s functionality. We’ll be going to explore a lot of architectural patterns in-depth there.

This part is mostly made up of longer notes or journal entries. They aren’t meant to tell a coherent story. They are meant to provide a view over my shoulder, include details and background information so you can follow along easily.

Think of this part as a log book to follow the path to adopting Swift. You may want to skim most of this part if you’re comfortable with Swift, XCTest, and Core Data already.

Functional Testing Helps Transition to Adding Real Features

I’m pretty confident the view does what it should do. It’s all just basic stuff, although it took enough trial & error to reach this point. (I am talking about you, explicit Cocoa Bindings which break the view’s behavior!)

I am curious if the app will work, though. This is only natural. Developers want to have a running prototype as early as possible. I’m no exception. Unit tests can help to get feedback that algorithms work. But you won’t know if the app executes and performs anything at all until you run it. There are two steps I could take at this point to quench my curiosity.

First, I could use more unit tests to verify that view model updates result in NSOutlineView updates. These updates should also be visible when I run the app, but the unit tests won’t give visual feedback. Still, I would verify that the controller logic works. Since the data doesn’t change in the background but depend on me clicking on widgets, I’m eager to see Cocoa Bindings in action. Then again, I’ve already witnessed Cocoa Bindings in another project. It felt like magic when the label updated to a new value that the component received after a couple of idle time automatically. Key-Value Observing is doing a lot of the hard work here. I can assure you that the Cocoa Bindings are set up correctly; in fact, the binding tests verify that the setup is working. Seeing the changes in the running app equals a manual integration test – that is testing Apple’s internal frameworks to do their job properly. That doesn’t make any sense. I would like to see it in action, but it won’t provide any useful information.

Second, I could add functional tests to the test harness. And this is what I’ll do: in a fashion you usually find with Behavior-Driven Development (as opposed to Test-Driven Development, where you start at the innermost unit level), I’ll add a failing test which will need the whole app to work together in order to pass. It’s a test which is going to be failing for quite a while. It’s an automated integration test that exercises various layers of the app at once.

In the past I wrote failing functional tests for web applications only because they would drive out the REST API or user interaction pretty well. I haven’t tried this for iOS or Mac applications, yet. So let’s do this.

If you work by yourself, I think it’s okay to check in your code with a failing test as long as it’s guiding development. Don’t check in failing unit tests just because you can’t figure out how to fix the problem immediately. Everything goes as long as you don’t push the changes to a remote repository. Until that point, you can always alter the commit history.

Having a failing functional test at all may not be okay with your team mates, though. After all, your versioning system should always be in a valid state, ready to build and to pass continuous deployment. Better not check in the failing test itself, then, or work on a local branch exclusively until you rebase your commit history when your feature is ready.

The bad thing about functional tests or integration tests is this: if all you had were functional tests, you’d have to write a ton of them. With every condition, with every fork in the path of execution, the amount of functional tests to write grows by a factor of 2. Functional tests grow exponentially. That’s bad.

So don’t rely too much on them. Unit tests are the way to go.

And make sure you watch J. B. Rainsberger’s “Integrated Tests are a Scam” some day soon. It’s really worth it.

That being said, let’s create a functional test to learn how things work together and guide development.

First Attempt at Capturing the Expected Outcome

I want to go from user interface actions all down to Core Data. That’s a first step. This is the test I came up with:

 1 func testAddFirstBox_CreatesBoxRecord() {
 2     // Precondition
 3     XCTAssertEqual(repository.count(), 0, "repo starts empty")
 4     
 5     // When
 6     viewController.addBox(self)
 7 
 8     // Then
 9     XCTAssertEqual(repository.count(), 1, "stores box record")
10     XCTAssertEqual(allBoxes().first?.title, "New Box")
11 }

There’s room for improvement: I expect that there’s a record with a given BoxId afterwards, and that the view model contains a BoxNode with the same identifier. This is how the view, domain, and Core Data stay in sync. But the naive attempt at querying the NSTreeController is hideous:

viewController.itemsController.arrangedObjects.childNodes!!.first.boxId

I rather defer such tests until later to avoid these train wreck-calls and stick with the basic test from above that at least indicates the count() did change, even though I don’t know if the correct entity was inserted at this point.

Making Optionals-based Tests Useful

On a side note, I think Swift’s optionals are making some tests weird. With modern Swift, optional chaining and XCTAssertEqual play together nicely:

XCTAssertEqual(allBoxes().first?.title, "New Box")

Sometimes, you need to unwrap an optional, though. Force-unwrapping nil results in a runtime error which we want to avoid during tests because it interrupts execution of the whole test suite. A failure is preferable.

if let box = allBoxes().first {
    XCTAssertEqual(box.title, "New Box")
} else {
    XTCFail("expected 1 or more boxes")
}

Imagine allBoxes() returned an optional; then the complexity of the test would increase a lot:

if let boxes = allBoxes() {
    if let box: ManagedBox = allBoxes().first {
        XCTAssertEqual(box.title, "New Box")
    } else {
        XCTFail("no boxes found")
    }
} else {
    XCTFail("boxes request invalid")
}

When you work with collections, instead of nil, return an empty array. This makes the result much more predictable for the client. Here’s the updated version:

Don’t allow optionals if there’s no need to
 1 func allBoxes() -> [ManagedBox] {
 2     let request = NSFetchRequest(entityName: ManagedBox.entityName())
 3     let results: [AnyObject]
 4     
 5     do {
 6         try results = context.fetch(request)
 7     } catch {
 8         XCTFail("fetching all boxes failed")
 9         return []
10     }
11 
12     guard let boxes = results as? [ManagedBox] else {
13         return []
14     }
15 
16     return boxes
17 }

If optional chaining doesn’t work for you for some reason, make it a two-step assertion instead:

let box: ManagedBox = allBoxes().first
XCTAssertNotNil(box)
if let box = box {
    XCTAssertEqual(box.title, "New Box")
}

If the value is nil, the first assertion will fail and the test case will be marked as a failing test. You can come back to it and fix it. No breaking runtime errors required!

Wire-Framing the Path to “Green”

Now this integration test fails, of course. In broad strokes, this is what’s left to do to connect the dots and make it pass:

  • I have to make a repository available to the view. I’ll do this via Application Services.
  • I have to add an actual Application Service layer. Remember, this is the client of the domain.
  • The Application Service will issue saving Boxes and Items without knowing about Core Data.
  • I need a service provider of sorts. Something somewhere has to tell the rest of the application that CoreDataBoxRepository (from Infrastructure) is the default implementation of the BoxRepository protocol (from the Domain). The process of setting this up takes place in the application delegate, but there’s a global ServiceLocator singleton missing to do the actual look-up.
  • I may need to replace the ServiceLocator’s objects with test doubles.

The ServiceLocator can look like this:

ServiceLocator singleton to select default implementations
 1 open class ServiceLocator {
 2     open static let sharedInstance = ServiceLocator()
 3     
 4     // MARK: Configuration
 5     
 6     fileprivate var managedObjectContext: NSManagedObjectContext?
 7 
 8     public func setManagedObjectContext(_ managedObjectContext: NSManagedObj\
 9 ectContext) {
10         precondition(self.managedObjectContext == nil, 
11             "managedObjectContext can be set up only once")
12             
13         self.managedObjectContext = managedObjectContext
14     }
15     
16     // MARK: Dependencies
17     
18     public class func boxRepository() -> BoxRepository {
19         return sharedInstance.boxRepository()
20     }
21     
22     // Override this during tests:
23     open func boxRepository() -> BoxRepository {
24         guard let managedObjectContext = self.managedObjectContext
25             else { preconditionFailure("managedObjectContext must be set up"\
26 ) }
27         
28         return CoreDataBoxRepository(managedObjectContext: managedObjectCont\
29 ext)
30     }    
31 }

It’s not very sophisticated, but it is enough to decouple the layers. An Application Service could now perform insertions like this:

1 public func provisionBox() -> BoxId {
2     let repository = ServiceLocator.boxRepository()
3     let boxId = repository.nextId()
4     let box = Box(boxId: boxId, title: "A Default Title")
5     
6     repository.addBox(box)
7     
8     return boxId
9 }

This is not a command-only method because it returns a value. Instead of returning the new ID so the view can add it to the node, the service will be responsible for actually adding the node to the view. That’d be the first major refactoring.1

It’s about time to leave the early set-up phase and enter Part II, where I’m going to deal with all these details and add the missing functionality.