How to Make Specialized Test Assertions in Swift

July 26, 2016 — 2 Comments

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 it 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))

That works in Swift 2.2 and greater. (There are generic operator functions that work on 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 is time 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 much 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) {

Conclusion

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! (But I expect to hit the problems soon…)

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

Jon Reid

Posts Twitter Facebook Google+

I've been practicing Test Driven Development (TDD) since 2001. Learn more on my About page.

2 responses to How to Make Specialized Test Assertions in Swift

  1. 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.

    Example:

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

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

    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 🙂

  2. 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.

Leave a Reply

Text formatting is available via select HTML.

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> 

*