Unwrap optional values in XCTest with XCTUnwrap

Xcode Unit Testing Semantic

Before Xcode 11, when I have to test an optional variable in XCTest, I used to do something like this.

Let's say we have a User model with an optional Address.

struct Address {
let city: String
let country: String
}

struct User {
let name: String
let address: Address?
}

Not lazy way

func testAddress() throws {
let userModel: User? = User(name: "John", address: nil)

guard let user = userModel else {
XCTFail("Expected non-nil user")
return
}

guard let address = user.address else {
XCTFail("Expected non-nil address")
return
}

XCTAssertEqual(address.city, "Bangkok")
XCTAssertEqual(address.country, "Thailand")
}

How you write a test is quite subjective, there is no right or wrong, but I think a separate guard statement sending a clearer message when the test failed.

If the test failed, you would get an error like this:

failed - Expected non-nil address

I know exactly the part that might go wrong when I see something like this. So this is my preferred way of testing optional values.

A quite lazy way

The following example is like the former one, just a bit lazier. I put multiple optional values under the same guard.

func testAddress() throws {
let userModel: User? = User(name: "John", address: nil)

guard
let user = userModel,
let address = user.address
else {
XCTFail("Expected non-nil user and address")
return
}

XCTAssertEqual(address.city, "Bangkok")
XCTAssertEqual(address.country, "Thailand")
}

The error won't be as clear as the first one. You can't be sure whether User or Address is nil.

failed - Expected non-nil user and address

Lazy way

I used this one a lot since it is the shortest, but the failed message is the least useful.

func testManualOptional3c() throws {
let user: User? = User(name: "John", address: nil)

XCTAssertEqual(user?.address?.city, "Bangkok")
XCTAssertEqual(user?.address?.country, "Thailand")
}

You will get two failed cases, which are quite vague. User, Address, or the property itself is nil; you would never know.

failed: ("nil") is not equal to ("Optional("Bangkok")")
failed: ("nil") is not equal to ("Optional("Thailand")")

XCTUnwrap to the rescue

In Xcode 11, there is a new assertion for the unwrapping. XCTUnwrap[1] is an assertion that tries to unwrap the optional and throw an error (and thus fail the test) if the optional is nil. XCTUnwrap is perfect for this situation.

func testAddress() throws {
let userModel: User? = User(name: "John", address: nil)
let user = try XCTUnwrap(user)
let address = try XCTUnwrap(u.address)

XCTAssertEqual(address.city, "Bangkok")
XCTAssertEqual(address.country, "Thailand")
}

The error is as clear as our first example, but a lot shorter.

failed: expected non-nil value of type "Address"

  1. Asserts that an expression is not nil and returns the unwrapped value. XCTUnwrap ↩︎

If you enjoy this article, you can subscribe to the weekly newsletter.

Every Friday, you’ll get a quick recap of all articles and tips posted on this site — entirely for free.


← Home