Unwrap optional values in XCTest with XCTUnwrap
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.
Localization Buddy: Easiest way to localize and update App Store metadata.
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.
Localization Buddy: Easiest way to localize and update App Store metadata.
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"
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