Quality Coding
Emoji: Clamp

How to Make Specialized Test Assertions in Swift


When a test fails, we want to know the location of the failure. Getting this information in Objective-C required us to dance with the preprocessor. But with Swift, it’s much more straightforward.

Old days: preprocessor macros

Objective-C is a language with two sides: the C side, and (practically speaking) the Smalltalk side. The C side not only gave us the C language, but the C preprocessor. The preprocessor is a way to manipulate the code you write into the code that is actually compiled. We used it all the time without giving it much thought. Every #import statement is a preprocessor directive to insert text, and so on.

The preprocessor could do magic, for good or ill. I learned to avoid Xcode preprocessor macros as much as possible. But sometimes it was unavoidable. The predefined macros __FILE__ and __LINE__ expanded to the name of the current file, and the current line number. These give us the information we need to report test failures.

But some people assumed this meant you had to write preprocessor macros for everything that used this information. This led to the unfortunate pattern of Objective-C XCTest assertions written entirely as macros. (By contrast, my approach with OCHamcrest and OCMockito was to get the file name and line number and get out. That is, I passed them as arguments to ordinary functions and methods.)

New days: Swift literal expressions

Swift directly incorporates these important values into the language as “literal expressions”. They are #file and #line. You can use them anywhere, but they’re particularly useful as default parameter values.

Do you know the assertion XCTFail? That’s the one that will always report a failure. Let’s look at its signature:

public func XCTFail(_ message: String = default,
                    file: StaticString = #file, line: UInt = #line)

There are 3 parameters: an optional message, an optional file name, and an optional line number. We usually call this without specifying the last two parameters. Their default values are #file and #line, which evaluate to their position at the point of call. This is important. We don’t get the location of the function. Instead, we get the location of the place that calls the function.

Motivating example: Testing tuples

Let’s say you want a test that confirms the values of a tuple. We’ll use a pair of integers for simplicity. How would you write a test to check its values? I’d first try this:

XCTAssertEqual(pair, (3, 11))

But this doesn’t work because tuples are currently not Equatable. OK, then how about this?

XCTAssert(pair == (3, 11))

This works because there are generic operators for pairs and other tuples. But consider what it reports upon failure:

XCTAssertTrue failed –

All it tells us is that the assertion failed. It doesn’t tell us why. Every time you have to fire up the debugger to analyze a test failure, time is wasted. Why can’t we get this information the first time around?

The XCTest assertions all have an optional message parameter. So we can use this to report the actual value:

let pair = (1, 2)
XCTAssert(pair == (3, 11), "was \(pair)")

This gives us the following message:

XCTAssertTrue failed – was (1, 2)

That’s better! But having to repeat this code for every pair we test… that’s duplicate code, crying out for refactoring. Time to apply “Extract Method”…

Writing a custom test assertion

Let’s use what we learned about #file and #line. We can start writing a function to check pairs of integers:

func assertIntPairsEqual(
        actual: (_: Int, _: Int),
        expected: (_: Int, _: Int),
        file: StaticString = #file, line: UInt = #line)

Now we know that within this function, the file and line parameters will reflect the location of the point of call.

Writing the comparison is easy. What do we do if it fails? We want to report it to XCTest.

Here’s the trick: Remember XCTFail? It has those optional parameters for file and line. What if we don’t use their default values, but specify our own? And the optional message while we’re at it:

func assertIntPairsEqual(
        actual: (_: Int, _: Int),
        expected: (_: Int, _: Int),
        file: StaticString = #file, line: UInt = #line) {
    if actual != expected {
        XCTFail("Expected \(expected) but was \(actual)",
                file: file, line: line)
let pair = (1, 2)
assertIntPairsEqual(actual: pair, expected: (3, 11))

This produces the following message:

failed – Expected (3, 11) but was (1, 2)

There we go! To top it off, we can make it more generic. The following assertion can handle any pairs of any Equatable types T and U:

func assertPairsEqual<T: Equatable, U: Equatable>(
        actual: (_: T, _: U),
        expected: (_: T, _: U),
        file: StaticString = #file, line: UInt = #line) {


Here are the steps for creating specialized test assertions in Swift:

  • Define your assertion as a helper function.
  • Design the parameters to be unambiguous.
  • Include optional parameters for file and line.
  • Upon failure, call XCTFail, passing the file and line arguments.
  • Report all the information you need to diagnose failures.
  • Can you make the assertion generic?

Swift makes this aspect of testing easy!

Have you written any custom test assertions in Swift? Please describe them below!

About the Author Jon Reid

Programming was fun when I was a kid. But working in Silicon Valley, I saw poor code lead to fear, with real human costs. Looking for ways to make my life better, I learned about Design Patterns, Refactoring, and Test-Driven Development (TDD). Programming became fun again! I've now been doing TDD in Apple environments for 18 years. I'm committed to software crafting as a discipline, hoping we can all reach greater effectiveness and joy.

follow me on:
Disclosure: The links below are affiliate links. If you buy anything, I earn a commission, at no extra cost to you.

  • I like this technique and would like to add that with Swift you can even define a func inside another func. For example when you want to do multiple similar assertions during one test.


    func testReset() {
    func assertInDefaultState(foo: Foo, line: UInt = #line) {
    XCTAssertTrue(foo.bar, line: line)
    XCTAssertFalse(foo.foo, line: line)
    // ...

    let foo = Foo()
    foo.bar = false
    // more modifications and possibly assertions

    One more example I’d like to share is how I assert that two NSIndexPaths are the same.

    func assertEqualIndexPaths(first: NSIndexPath, _ second: NSIndexPath, line: UInt = #line) {
    let a = (section: first.section, row: first.row)
    let b = (section: second.section, row: second.row)

    XCTAssertTrue(a == b, "\(a) != \(b)", line: line)

    I use it in one place in one file, so I don’t use #file here. It’s actually also just a function defined inside test function.

    Hope there ideas will help someone :)

  • Dan Cutting says:

    Hi Jon, great post! I had been wondering if there were a simple way to report the right line numbers in my helper verification functions, and this is a great solution.

  • >