Unwrap optional values in XCTest with XCTUnwrap
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"