Clean up your Xcode unit tests
Tags: swift, xcode, ios
Published: 2019-03-13 22:05:46

---
title: Clean up your Xcode unit tests
published: true
description: Write custom assertions and refactor your tests to keep your Xcode tests clean.
tags: swift, xcode, iOS
---

Apple's XCTest framework for Xcode offers only a handful of assertion statements, and almost all of them are trivial variations of `XCTAssert`. If you find yourself calling `XCTAssertEqual` over and over again in your tests, consider writing your own assertion functions! You may find that your test code is cleaner as a result.

Consider the following example: I have an unsorted list of contacts, and I want to make sure they are sorted alphabetically by last name.

```swift
func testSortAlphabeticallyByLastName() {
    let contact1 = Contact(firstName: "John", lastName: "Appleseed")
    let contact2 = Contact(firstName: "Paul", lastName: "Bunyan")
    let contact3 = Contact(firstName: "Davy", lastName: "Crockett")
    let unsortedContacts = [contact2, contact3, contact1]

    let sortedContacts = ContactSorter().sort(unsortedContacts, by: .lastName)

    // Contact does not conform to Equatable, so we test each element separately
    XCTAssertEqual(sortedContacts[0].lastName, "Appleseed")
    XCTAssertEqual(sortedContacts[1].lastName, "Bunyan")
    XCTAssertEqual(sortedContacts[2].lastName, "Crockett")
}
```

This can be cleaned up. I dislike the 3 separate calls to `XCTAssertEqual`, and I also dislike that we're comparing the last name instead of the whole contact. We *could* make `Contact` conform to `Equatable`, but for this exercise, let's try to do without.

First, I want to write an function that asserts a contact has a given name. I might write it like this.

```swift
func testSortAlphabeticallyByLastName() {
    let contact1 = Contact(firstName: "John", lastName: "Appleseed")
    let contact2 = Contact(firstName: "Paul", lastName: "Bunyan")
    let contact3 = Contact(firstName: "Davy", lastName: "Crockett")
    let unsortedContacts = [contact2, contact3, contact1]
    
    let sortedContacts = ContactSorter().sort(unsortedContacts, by: .lastName)
    
    assert(sortedContacts[0], hasName: "John Appleseed")
    assert(sortedContacts[1], hasName: "Paul Bunyan")
    assert(sortedContacts[2], hasName: "Davy Crockett")
}

private func assert(_ contact: Contact, hasName fullName: String) {
    let contactFullName = "\(contact.firstName ?? "") \(contact.lastName ?? "")"
    XCTAssertEqual(contactFullName, fullName)
}
```

There's just one problem. If the tests fail, Xcode highlights the line for `XCTAssertEqual` inside my function, instead of the line where the function is called. This isn't very helpful.

![XCTAssertEqual failed: ("Davy Crockett") is not equal to ("John Appleseed")](https://thepracticaldev.s3.amazonaws.com/i/y6n4cph52c2bzocjuq0h.png)

Did you know that `XCTAssert` (and its derivatives) has arguments for file and line number? By default, they're populated by the caller using the `#file` and `#line` values. My `assert(_:hasName:)` function can capture these values and pass them into `XCTAssertEqual` to have Xcode highlight the correct offending line.

Here's my updated function:

```swift
private func assert(_ contact: Contact, hasName fullName: String, file: StaticString = #file, line: UInt = #line) {
    let contactFullName = "\(contact.firstName ?? "") \(contact.lastName ?? "")"
    XCTAssertEqual(contactFullName, fullName, file: file, line: line)
}
```

And here's the result:

![XCTAssertEqual failed: ("Davy Crockett") is not equal to ("John Appleseed")](https://thepracticaldev.s3.amazonaws.com/i/e9cx8s1x7dtj2w7a2dwe.png)

This is already much nicer. However, I would like to clean this up even more. Let's start by flattening those three separate assertions into one.

```swift
func testSortAlphabeticallyByLastName() {
    let contact1 = Contact(firstName: "John", lastName: "Appleseed")
    let contact2 = Contact(firstName: "Paul", lastName: "Bunyan")
    let contact3 = Contact(firstName: "Davy", lastName: "Crockett")
    let unsortedContacts = [contact2, contact3, contact1]
    
    let sortedContacts = ContactSorter().sort(unsortedContacts, by: .lastName)
    
    assert(sortedContacts, areNamed: [
        "John Appleseed", 
        "Paul Bunyan", 
        "Davy Crockett"])
}

private func assert(_ contacts: [Contact], areNamed names: [String], file: StaticString = #file, line: UInt = #line) {
    contacts.enumerated().forEach { (index, contact) in
        let name = names[index]
        assert(contact, hasName: name, file: file, line: line)
    }
}

private func assert(_ contact: Contact, hasName fullName: String, file: StaticString = #file, line: UInt = #line) {
    let contactFullName = "\(contact.firstName ?? "") \(contact.lastName ?? "")"
    XCTAssertEqual(contactFullName, fullName, file: file, line: line)
}
```

Next, let's create a convenience function for contact initialization. I'm not testing the contact initializer here, so we should refactor that out of the test body.

```swift
func testSortAlphabeticallyByLastName() {
    let contact1 = makeContact(named: "John Appleseed")
    let contact2 = makeContact(named: "Paul Bunyan")
    let contact3 = makeContact(named: "Davy Crockett")
    let unsortedContacts = [contact2, contact3, contact1]
    
    let sortedContacts = ContactSorter().sort(unsortedContacts, by: .lastName)
    
    assert(sortedContacts, areNamed: [
        "John Appleseed", 
        "Paul Bunyan", 
        "Davy Crockett"])
}

private func makeContact(named name: String) -> Contact {
    let names = name.split(separator: " ")
    let firstName = String(names.first!)
    let lastName = String(names.last!)
    return Contact(firstName: firstName, lastName: lastName)
}
```

Let's go one step further and inline the contact names into one function.

```swift
func testSortAlphabeticallyByLastName() {
    let unsortedContacts = makeContacts(named: [
        "Paul Bunyan",
        "Davy Crockett",
        "John Appleseed"])
    
    let sortedContacts = ContactSorter().sort(unsortedContacts, by: .lastName)
    
    assert(sortedContacts, areNamed: [
        "John Appleseed",
        "Paul Bunyan",
        "Davy Crockett"])
}

private func makeContacts(named names: [String]) -> [Contact] {
    return names.map(makeContact(named:))
}
```

I don't think this could get much cleaner! One call to set up our data, one call to exercise our code, and one call to check our work. Now let's add a test for sorting by first name.

```swift
func testSortAlphabeticallyByFirstName() {
    let unsortedContacts = makeContacts(named: [
        "Paul Bunyan",
        "Davy Crockett",
        "John Appleseed"])
    
    let sortedContacts = ContactSorter().sort(unsortedContacts, by: .firstName)
    
    assert(sortedContacts, areNamed: [
        "Davy Crockett",
        "John Appleseed",
        "Paul Bunyan"])
}
```