Reader small image

You're reading from  Test-Driven iOS Development with Swift - Fourth Edition

Product typeBook
Published inApr 2022
PublisherPackt
ISBN-139781803232485
Edition4th Edition
Right arrow
Author (1)
Dr. Dominik Hauser
Dr. Dominik Hauser
author image
Dr. Dominik Hauser

Dr. Dominik Hauser is an iOS developer working for a small company in western Germany. In over 11 years as an iOS developer, he has worked on many different apps, both large and small. In his spare time, Dominik builds small (often silly) apps and explores how he can become a better iOS developer. He talks at conferences, writes books, and is active in the iOS community in general. His most successful open source component (in terms of stars) is a pull-to-refresh control for a table view that consists of an actual SpriteKit game. Before Dominik became an iOS developer, he was a physicist, researching the most powerful photon sources within our galaxy.
Read more about Dr. Dominik Hauser

Right arrow

Assert functions in the XCTest framework

Each test needs to assert some expected behavior. The use of XCTAssert functions tells Xcode what is expected.

A test method without an XCTAssert function that doesn't throw an error will always pass.

The most important assert functions are listed here:

  • XCTAssertTrue(_:_:file:line:): This asserts that an expression is true.
  • XCTAssert(_:_:file:line:): This assertion is the same as XCTAssertTrue(_:_:file:line:).
  • XCTAssertFalse(_:_:file:line:): This asserts that an expression is false.
  • XCTAssertEqual(_:_:_:file:line:): This asserts that two expressions are equal.
  • XCTAssertEqual(_:_:accuracy:_:file:line:): This asserts that two expressions are the same, taking into account the accuracy defined in the accuracy parameter.
  • XCTAssertNotEqual(_:_:_:file:line:): This asserts that two expressions are not equal.
  • XCTAssertNotEqual(_:_:accuracy:_:file:line:): This asserts that two expressions are not the same, taking into account the accuracy defined in the accuracy parameter.
  • XCTAssertNil(_:_:file:line:): This asserts that an expression is nil.
  • XCTAssertNotNil(_:_:file:line:): This asserts that an expression is not nil.
  • XCTFail(_:file:line:): This always fails.

To take a look at a full list of the available XCTAssert functions, press Ctrl and click on the XCTAssertEqual word in the test that you have just written. Then, select Jump to Definition in the pop-up menu, as shown in the following screenshot:

Figure 1.5 – Jump to Definition of a selected function

Figure 1.5 – Jump to Definition of a selected function

Note that most XCTAssert functions can be replaced with XCTAssert(_:_:file:line). For example, the following assert functions are asserting the same thing:

// This assertion asserts the same as...
XCTAssertEqual(2, 1+1, "2 should be the same as 1+1")
// ...this assertion
XCTAssertTrue(2 == 1+1, "2 should be the same as 1+1")

But you should use more precise assertions whenever possible, as the log output of the more precise assertion methods tells you exactly what happened in case of a failure. For example, look at the log output of the following two assertions:

XCTAssertEqual(1, 2)
// Log output:
// XCTAssertEqual failed: ("1") is not equal to ("2")
XCTAssert(1 == 2)
// Log output:
// XCTAssertTrue failed

In the first case, you don't need to look at the test to understand what happened. The log tells you exactly what went wrong.

Custom assert functions

But sometimes, even the more precise assert function is not precise enough. In this case, you can write your own assert functions. As an example, let's assume we have a test that asserts that two dictionaries have the same content. If we used XCTAssertEqual to test that, the log output would look like this:

func test_dictsAreQual() {
  let dict1 = ["id": "2", "name": "foo"]
  let dict2 = ["id": "2", "name": "fo"]
  XCTAssertEqual(dict1, dict2)
  // Log output:
  // XCTAssertEqual failed: ("["name": "foo", "id":
    "2"]")...
  // ...is not equal to ("["name": "fo", "id": "2"]")
}

For the short dictionaries in this example, finding the difference is quite easy. But what if the dictionary has 20 entries or even more? When we add the following assert function to the test target, we get better log outputs:

func DDHAssertEqual<A: Equatable, B: Equatable>
  (_ first: [A:B],
   _ second: [A:B]) {
  if first == second {
    return
  }
  for key in first.keys {
    if first[key] != second[key] {
      let value1 = String(describing: first[key]!)
      let value2 = String(describing: second[key]!)
      let keyValue1 = "\"\(key)\": \(value1)"
      let keyValue2 = "\"\(key)\": \(value2)"
      let message = "\(keyValue1) is not equal to
        \(keyValue2)"
      XCTFail(message)
      return
    }
  }
}

This method compares the values for each key and fails if one of the values differs. Additionally, this assert function should check whether the dictionaries have the same keys. This functionality is left as an exercise for the reader. Here, we focus this example on how to write a custom assert function. By keeping the example short, the main point is easier to understand.

When we run this test with the preceding dictionaries, we see the following output in Xcode:

Figure 1.6 – Xcode showing the failure at two different places

Figure 1.6 – Xcode showing the failure at two different places

As you can see in the preceding screenshot, Xcode shows the test failure in the assert function. In the test method, it only shows a redirect to the failure. Fortunately, there is an easy fix for that. All we have to do is to pass file and line parameters to the custom assert function and use these in the XCTFail call, like this:

 func DDHAssertEqual<A: Equatable, B: Equatable>(
  _ first: [A:B],
  _ second: [A:B],
  file: StaticString = #filePath,        // << new
  line: UInt = #line) {                  // << new
    if first == second {
      return
    }
    for key in first.keys {
      if first[key] != second[key] {
        let value1 = String(describing: first[key]!)
        let value2 = String(describing: second[key]!)
        let keyValue1 = "\"\(key)\": \(value1)"
        let keyValue2 = "\"\(key)\": \(value2)"
        let message = "\(keyValue1) is not equal to
          \(keyValue2)"
        XCTFail(message, file: file, line: line)  // << new
        return
      }
    }
  }

Note that our assert function now has two new parameters: file and line, with the default values #filePath and #line, respectively. When the function is called in a test method, these default parameters make sure that the file path and the line of the call site are passed into that assert function. These parameters are then forwarded into the XCTAssert functions (XCTFail in our case, but this works with all XCT... functions). As a result, the failure is now shown in the line in which the DDHAssertEqual function is called, and we didn't have to change the call of the assert function. The following screenshot illustrates this:

Figure 1.7 – Improved failure reporting

This example shows how easy it is to write your own assert functions that behave like the ones that come with Xcode. Custom assert functions can improve the readability of the test code, but keep in mind that this is also code you have to maintain.

Previous PageNext Page
You have been reading a chapter from
Test-Driven iOS Development with Swift - Fourth Edition
Published in: Apr 2022Publisher: PacktISBN-13: 9781803232485
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
undefined
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €14.99/month. Cancel anytime

Author (1)

author image
Dr. Dominik Hauser

Dr. Dominik Hauser is an iOS developer working for a small company in western Germany. In over 11 years as an iOS developer, he has worked on many different apps, both large and small. In his spare time, Dominik builds small (often silly) apps and explores how he can become a better iOS developer. He talks at conferences, writes books, and is active in the iOS community in general. His most successful open source component (in terms of stars) is a pull-to-refresh control for a table view that consists of an actual SpriteKit game. Before Dominik became an iOS developer, he was a physicist, researching the most powerful photon sources within our galaxy.
Read more about Dr. Dominik Hauser