Unwrap optional values in XCTest with XCTUnwrap

⋅ 2 min read ⋅ Xcode Unit Testing Semantic

Table of Contents

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.

You can easily support sarunw.com by checking out this sponsor.

Sponsor sarunw.com and reach thousands of iOS developers.

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

You can easily support sarunw.com by checking out this sponsor.

Sponsor sarunw.com and reach thousands of iOS developers.

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


Read more article about Xcode, Unit Testing, Semantic, or see all available topic

Enjoy the read?

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. No strings attached. Unsubscribe anytime.

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 Buy me a coffee Tweet Share
Previous
// MARK: - What is it?

If this is just a comment for you, this article might benefit you.

Next
Swift Documentation

How to write documentation comments in your Swift code.

← Home