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 asXCTAssertTrue(_:_: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 theaccuracy
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 theaccuracy
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:
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:
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:
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.