Updated At and Created At

Let's start with the updatedAtAsDate computed property. Change the name of the unit test to testUpdatedAtAsDate_NotNil(). We need to implement a second unit test for the updatedAtAsDate computed property if the updatedAt property doesn't have a value.

In that scenario, the updatedAtAsDate computed property returns the current time and date. That's a bit of a problem, though. The current time and date will always differ slightly between the moment we ask for the value of the updatedAtAsDate computed property and the moment we compare it with the current date and time. Let me show you what I mean.

Create a new unit test and name it testUpdatedAtAsDate_Nil(). Notice that I use a specific convention for naming the unit tests we write. Each unit test starts with the word test, which is required, followed by the name of the method or computed property.

NoteTests.swift

func testUpdatedAtAsDate_Nil() {

}

If I need to write multiple unit tests for a method or computed property, I append a descriptive keyword to the name of the unit test, using an underscore for readability. This is a personal choice that I like because it makes the unit tests easier to read. If the names of the unit tests aren't descriptive, for example, testText1() and testText2(), you need to read the implementation of the unit test to understand what they test and how they differ. Give it a try and see if you like it.

The implementation of the unit test looks similar to that of testUpdatedAtAsDate_NotNil(). The only difference is that we explicitly set the value of the updatedAt property to nil and assert that the value returned by the updatedAtAsDate computed property is equal to the current date and time. We don't have to set the value of the updatedAt property to nil since its value defaults to nil, but it makes the unit test easier to understand.

NoteTests.swift

func testUpdatedAtAsDate_Nil() {
    // Create Note
    let note = Note(context: managedObjectContext)

    // Populate Note
    note.updatedAt = nil

    // Assertions
    XCTAssertEqual(note.updatedAtAsDate, Date())
}

Run the unit test by clicking the diamond in the gutter on the left. Are you surprised by the result? The message Xcode shows us isn't very helpful. The message seems to indicate that the dates are identical and the unit test should pass. The raw values of the Date instances are different, though.

A Failed Unit Test

We can verify this by printing the raw value to the console. Run the unit test again and inspect the output in the console.

NoteTests.swift

func testUpdatedAtAsDate_Nil() {
    // Create Note
    let note = Note(context: managedObjectContext)

    // Populate Note
    note.updatedAt = nil

    // Assertions
    XCTAssertEqual(note.updatedAtAsDate, Date())

    print(note.updatedAtAsDate.timeIntervalSince1970)
    print(Date().timeIntervalSince1970)
}

NoteTests.swift

1501058374.3804
1501058374.38068

The difference is small, but that's the point of the unit test. We don't test whether the dates are more or less the same. We test for equality. There's no easy or elegant solution to resolve this issue.

This isn't a major issue, though. Remember that we implemented the updatedAtAsDate computed property to avoid optionals. If the updatedAt property doesn't have a value, we don't care what's returned to us, as long as it's a Date instance.

To complete the unit test, we assert that the value of updatedAtAsDate isn't equal to nil. That seems like a good idea. Right? The unit test passes and that isn't a surprise. The updatedAtAsDate computed property returns a Date instance, which means the unit test passed by default.

NoteTests.swift

func testUpdatedAtAsDate_Nil() {
    // Create Note
    let note = Note(context: managedObjectContext)

    // Populate Note
    note.updatedAt = nil

    // Assertions
    XCTAssertNotNil(note.updatedAtAsDate)
}

Run the test suite to make sure every unit test succeeds. Revisit Note.swift and take a look at the code coverage for the updatedAtAsDate computed property. That looks better.

Code Coverage in Xcode

But what did we gain by adding the second unit test for the updatedAtAsDate computed property? The second unit test always passes. The only change we can see is that the updatedAtAsDate computed property is now fully covered. It's important to understand that we improved code coverage by adding a questionable unit test. This isn't wrong, but it's important to be aware of that.

We can do the same for the createdAtAsDate computed property. We rename the unit test we implemented earlier and we add a new unit test for the scenario in which the value of the createdAt property is equal to nil.

NoteTests.swift

func testCreatedAtAsDate_NotNil() {
    // Helpers
    let createdAt = Date(timeIntervalSince1970: 1500911077)

    // Create Note
    let note = Note(context: managedObjectContext)

    // Populate Note
    note.createdAt = createdAt

    // Assertions
    XCTAssertEqual(note.createdAtAsDate, createdAt)
}

func testCreatedAtAsDate_Nil() {
    // Create Note
    let note = Note(context: managedObjectContext)

    // Populate Note
    note.createdAt = nil

    // Assertions
    XCTAssertNotNil(note.createdAtAsDate)
}

Alphabetizing Tags

While I love the addition of code coverage in Xcode, it's important to keep in mind that it isn't a perfect tool. It only gives you an approximation of the project's code coverage. It isn't the absolute truth.

Open Note.swift and take a look at the code coverage for the alphabetizedTags computed property. Several edge cases are not covered by unit tests. Let's start with the guard statement at the top.

Unit Testing Fatal Errors

Let's take a close look at the condition of the guard statement. We downcast the value of the tags property to a set of Tag instances using the as? operator. If the downcast fails, we return nil.

You may be wondering how we can test this in a unit test? That's the wrong question, though. The question is "Should we test this scenario in a unit test?" The downcast should always succeed. If the value of the tags property can't be downcast to a set of Tag instances, the application has entered a state it doesn't know how to handle, an unexpected state. Whenever that happens, I choose to throw a fatal error.

Note.swift

// MARK: - Tags

var alphabetizedTags: [Tag]? {
    guard let tags = tags as? Set<Tag> else {
        fatalError()
    }

    return tags.sorted(by: {
        guard let tag0 = $0.name else { return true }
        guard let tag1 = $1.name else { return true }
        return tag0 < tag1
    })
}

Throwing a fatal error may seem like an easy way out, but it isn't. Whenever an application encounters an unexpected state, it doesn't know how to proceed.

As a rule of thumb, I throw a fatal error instead of writing ugly code that makes me feel as if I've properly handled a situation I didn't expect. Don't lie to yourself, though. Unexpected means that you didn't anticipate it and, consequently, your application doesn't know how to recover from it.

Does this solve the problem? Yes and no. I never unit test fatal errors. Why would you write unit tests for a scenario you don't expect. It means that we cannot provide complete code coverage for the Note class. That's fine, though. Having complete code coverage is a pipe dream anyway.

There's one other option I didn't discuss yet. We could get rid of the guard statement by force unwrapping the value stored in the tags property. However, that's a path I choose not to take. That's a personal choice. A fatal error conveys a clear, unmistakable message. Force unwrapping a value is very, very often a shortcut you shouldn't take.

But there's also good news. Because we no longer return nil from the body of the guard statement, the type of the alphabetizedTags computed property no longer needs to be an optional type.

Note.swift

// MARK: - Tags

var alphabetizedTags: [Tag] {
    guard let tags = tags as? Set<Tag> else {
        fatalError()
    }

    return tags.sorted(by: {
        guard let tag0 = $0.name else { return true }
        guard let tag1 = $1.name else { return true }
        return tag0 < tag1
    })
}

This subtle change allows us to simplify the implementation of the alphabetizedTagsAsString computed property.

Note.swift

var alphabetizedTagsAsString: String? {
    guard alphabetizedTags.count > 0 else {
        return nil
    }

    let names = alphabetizedTags.flatMap { $0.name }
    return names.joined(separator: ", ")
}

We also need to update the implementation of testAlphabetizedTags().

NoteTests.swift

func testAlphabetizedTags() {
    // Helpers
    let tagsAsStrings = ["Gamma", "Alpha", "Beta"]
    let alphabetizedTagsAsStrings = ["Alpha", "Beta", "Gamma"]

    // Create Note
    let note = createNote(in: managedObjectContext, with: tagsAsStrings)

    // Invoke Method to Test
    let tags = note.alphabetizedTags

    // Assertions
    XCTAssertNotNil(tags)
    XCTAssertEqual(tags.count, alphabetizedTagsAsStrings.count)

    for (index, tag) in tags.enumerated() {
        XCTAssertEqual(tag.name, alphabetizedTagsAsStrings[index])
    }
}

A Tag Without a Name

We need to write a few more unit tests. If you take a look at the implementation of alphabetizedTags, you can see what those unit tests should test. What happens if a tag doesn't have a name? Let's find out by writing another unit test. We name the unit test testAlphabetizedTags_TagWithoutName() and rename the unit test we already have to testAlphabetizedTags_HasTags(). This is what the new unit test could look like.

NoteTests.swift

func testAlphabetizedTags_TagWithoutName() {
    // Helpers
    let tagsAsStrings = ["Gamma", "Alpha", "Beta"]
    let alphabetizedTagsAsStrings = ["Alpha", "Beta", "Gamma"]

    // Create Note
    let note = createNote(in: managedObjectContext, with: tagsAsStrings)

    // Add Tag Without Name
    let tag = Tag(context: managedObjectContext)
    tag.name = nil
    note.addToTags(tag)

    // Invoke Method to Test
    let tags = note.alphabetizedTags

    // Assertions
    XCTAssertNotNil(tags)
    XCTAssertEqual(tags.count, alphabetizedTagsAsStrings.count + 1)
}

We create a note with three tags and we add a fourth tag that doesn't have a name. The implementation of alphabetizedTags sorts the tags based on the name of the tag.

To be honest, I don't like the current implementation of alphabetizedTags. A better approach would be to ignore tags that don't have a name. The name attribute of the Tag entity is required and a tag without a name isn't very useful.

We use flatMap(_:) to filter out tags that don't have a name and we invoke sorted(by:) to sort the filtered tags by name. If a tag doesn't have a name, we throw a fatal error in the closure we pass to the sorted(by:) method.

Note.swift

var alphabetizedTags: [Tag] {
    guard let tags = tags as? Set<Tag> else {
        fatalError()
    }

    let filterdTags = tags.flatMap { $0.name != nil ? $0 : nil }

    return filterdTags.sorted(by: {
        guard let tag0 = $0.name else { fatalError() }
        guard let tag1 = $1.name else { fatalError() }
        return tag0 < tag1
    })
}

You could shorten the implementation by force unwrapping the value stored in the tag's name property, but I prefer throwing a fatal error if that means I can avoid force unwrapping the value of an optional. That's a personal choice.

Note.swift

// MARK: - Tags

var alphabetizedTags: [Tag] {
    guard let tags = tags as? Set<Tag> else {
        fatalError()
    }

    let filterdTags = tags.flatMap { $0.name != nil ? $0 : nil }

    return filterdTags.sorted(by: { $0.name! < $1.name! })
}

If you want to be clever or fancy, you could pass the result of flatMap(_:) directly to sorted(by:).

Note.swift

// MARK: - Tags

var alphabetizedTags: [Tag] {
    guard let tags = tags as? Set<Tag> else {
        fatalError()
    }

    return tags
        .flatMap({ $0.name != nil ? $0 : nil })
        .sorted(by: { $0.name! < $1.name! })
}

Because we're filtering out tags without a name, we need to update the implementation of testAlphabetizedTags_TagWithoutName(). The implementation is similar to that of the testAlphabetizedTags_HasTags() method. The only difference is that we add a tag without a name.

NoteTests.swift

func testAlphabetizedTags_TagWithoutName() {
    // Helpers
    let tagsAsStrings = ["Gamma", "Alpha", "Beta"]
    let alphabetizedTagsAsStrings = ["Alpha", "Beta", "Gamma"]

    // Create Note
    let note = createNote(in: managedObjectContext, with: tagsAsStrings)

    // Add Tag Without Name
    let tag = Tag(context: managedObjectContext)
    tag.name = nil
    note.addToTags(tag)

    // Invoke Method to Test
    let tags = note.alphabetizedTags

    // Assertions
    XCTAssertNotNil(tags)
    XCTAssertEqual(tags.count, alphabetizedTagsAsStrings.count)

    for (index, tag) in tags.enumerated() {
        XCTAssertEqual(tag.name, alphabetizedTagsAsStrings[index])
    }
}

Alphabetizing Tags as String

We need to write two more unit tests for the alphabetizedTagsAsString computed property of the Note class. We start by renaming testAlphabetizedTagsAsString() to testAlphabetizedTagsAsString_HasTags().

NoteTests.swift

// MARK: - Tests for Alphabetized Tags as String

func testAlphabetizedTagsAsString_HasTags() {
    ...
}

Another Tag Without a Name

The first unit test covers the scenario in which the note contains a tag without a name. Because we updated the implementation of the alphabetizedTags computed property, tags without a name are no longer taken into account by the alphabetizedTagsAsString computed property.

The implementation of the unit test looks similar to that of testAlphabetizedTags_TagWithoutName(). We invoke createNote(in:with:) to create a note with three tags and we add a fourth tag that doesn't have a name. We assert that the value of alphabetizedTagsAsString is equal to the value stored in alphabetizedTagsAsString.

NoteTests.swift

func testAlphabetizedTagsAsString_TagWithoutName() {
    // Helpers
    let tagsAsStrings = ["Gamma", "Alpha", "Beta"]
    let alphabetizedTagsAsString = "Alpha, Beta, Gamma"

    // Create Note
    let note = createNote(in: managedObjectContext, with: tagsAsStrings)

    // Add Tag Without Name
    let tag = Tag(context: managedObjectContext)
    tag.name = nil
    note.addToTags(tag)

    // Invoke Method to Test
    let tagsAsString = note.alphabetizedTagsAsString

    // Assertions
    XCTAssertNotNil(tagsAsString)
    XCTAssertEqual(tagsAsString, alphabetizedTagsAsString)
}

A Note Without Tags

The second unit test we need to add for the alphabetizedTagsAsString computed property covers the scenario in which we're dealing with a note without tags. The implementation should look familiar by now.

We create a note by invoking createNote(in:with:), passing in the managed object context and an empty array. The return value is a Note instance without any tags. We assert that the value of alphabetizedTagsAsString is equal to nil.

NoteTests.swift

func testAlphabetizedTagsAsString_NoTags() {
    // Create Note
    let note = createNote(in: managedObjectContext, with: [])

    // Invoke Method to Test
    let tagsAsString = note.alphabetizedTagsAsString

    // Assertions
    XCTAssertNil(tagsAsString)
}

We could implement a similar unit test for the alphabetizedTags computed property. We currently don't unit test the scenario in which a note doesn't have any tags. This is another example of the limitations of code coverage. Xcode gives us the impression that we have fully covered the alphabetizedTags computed property, with the exception of fatal errors, but we haven't written a unit test for the alphabetizedTags computed property and a note without tags.

Better Unit Tests

Run the test suite to make sure every unit test passes. If we take a look at the code coverage of the Note class, we can see that we've done a pretty good job. The only holes are caused by the application throwing a fatal error. In the next episode, we unit test the Category class.