DEV Community

Avery Pierce
Avery Pierce

Posted on

2 1

Clean up your Xcode unit tests

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.

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")
}
Enter fullscreen mode Exit fullscreen mode

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.

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)
}
Enter fullscreen mode Exit fullscreen mode

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: (

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:

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)
}
Enter fullscreen mode Exit fullscreen mode

And here's the result:

XCTAssertEqual failed: (

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.

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)
}
Enter fullscreen mode Exit fullscreen mode

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.

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)
}
Enter fullscreen mode Exit fullscreen mode

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

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:))
}
Enter fullscreen mode Exit fullscreen mode

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.

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"])
}
Enter fullscreen mode Exit fullscreen mode

Quadratic AI

Quadratic AI – The Spreadsheet with AI, Code, and Connections

  • AI-Powered Insights: Ask questions in plain English and get instant visualizations
  • Multi-Language Support: Seamlessly switch between Python, SQL, and JavaScript in one workspace
  • Zero Setup Required: Connect to databases or drag-and-drop files straight from your browser
  • Live Collaboration: Work together in real-time, no matter where your team is located
  • Beyond Formulas: Tackle complex analysis that traditional spreadsheets can't handle

Get started for free.

Watch The Demo 📊✨

Top comments (0)

Image of PulumiUP 2025

Explore What’s Next in DevOps, IaC, and Security

Join us for demos, and learn trends, best practices, and lessons learned in Platform Engineering & DevOps, Cloud and IaC, and Security.

Save Your Spot

👋 Kindness is contagious

Engage with a wealth of insights in this thoughtful article, cherished by the supportive DEV Community. Coders of every background are encouraged to bring their perspectives and bolster our collective wisdom.

A sincere “thank you” often brightens someone’s day—share yours in the comments below!

On DEV, the act of sharing knowledge eases our journey and forges stronger community ties. Found value in this? A quick thank-you to the author can make a world of difference.

Okay