Articles, podcasts and news about Swift development, by John Sundell.

Using test assertion messages as comments

Published on 12 May 2020
Discover page available: Unit Testing

Often when writing tests, we might want to add some additional information to our code — for example in order to explain why a certain operation is being performed, to give debugging hints in case of a future failure, or to make the code easier to follow.

One way to do that is of course to use good old fashioned comments — like in the following test, which verifies that we’re able to add and then remove an item from a TodoList:

class TodoListTests: XCTestCase {
    func testAddingAndRemovingItem() {
        // First, add an item and make sure that it's added
        var list = TodoList()
        let item = list.addItem(named: "My item")
        XCTAssertTrue(list.containsItem(named: item.name))

        // Then, remove the item and make sure that it was removed
        list.removeItem(item)
        XCTAssertFalse(list.containsItem(named: item.name))
    }
}

There’s certainly nothing wrong with the above approach, but like we took a look at in “Writing self-documenting Swift code”, if we can find ways to let our code explain itself, then that can often remove much of the need for the above kind of contextual comments.

One way to do that within our testing code is to make use of the fact that we can pass custom messages to each of XCTest’s various assertion functions. That way, we can convert our comments into messages that are directly attached to our verification code — like this:

class TodoListTests: XCTestCase {
    func testAddingAndRemovingItem() {
        var list = TodoList()
        let item = list.addItem(named: "My item")
        
        XCTAssertTrue(
            list.containsItem(named: item.name),
            "A TodoList should contain an item that was added"
        )

        list.removeItem(item)
        
        XCTAssertFalse(
            list.containsItem(named: item.name),
            "Item should have been removed after calling 'removeItem()'"
        )
    }
}

The major benefit of the above approach is that not only will our messages still be fully readable within our source code — they’ll also be displayed within our logs when an assertion failed, which often makes it much easier to identify and debug test failures when using continuous integration.