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 ↩︎


Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.

If you enjoy my writing, please check out my Patreon https://www.patreon.com/sarunw and become my supporter. Sharing the article is also greatly appreciated.

Become a patron Tweet Share

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