Writing unit tests that handle optional values can be a pain. Force unwrapping is concise but causes a crash when it goes wrong. Conditional unwrapping is tiresome and clumsy. Apple added XCTUnwrap
in Xcode 11 to clean up the mess. It’s a throwing assertion that unwraps an optional. If the unwrap succeeds you get back the value. If it fails it throws an exception.
let value = try XCTUnwrap(optionalValue)
To see how this helps clean up your tests let’s look at an example.
What’s The Problem?
Here’s my situation. I’ve created a subclass of the Core Data NSPersistentContainer
that changes some store defaults. An NSPersistentContainer
can have more than one store so the persistentStoreDescriptions
property is an array:
var persistentStoreDescriptions: [NSPersistentStoreDescription]
I want to test I’ve set some of the store properties so I need to get the first description in the array. To allow for an empty array the first
method gives us back an optional which makes writing my test a pain:
// NSPersistentStoreDescription?
let storeDescription = container.persistentStoreDescriptions.first
Since I never expect the array to be empty I could force unwrap in my test:
func testShouldAddStoreAsynchronously() {
let container = CoreDataContainer(name: "MyModel")
let storeDescription = container.persistentStoreDescriptions.first!
XCTAssertTrue(storeDescription.shouldAddStoreAsynchronously)
}
Force unwrapping is concise but has the disadvantage of crashing and aborting our test run when something goes wrong:
We can convert the crash to a failing test by testing for nil and conditionally unwrapping but it ends up looking clumsy. For example:
let container = CoreDataContainer(name: modelName)
let storeDescription = container.persistentStoreDescriptions.first
XCTAssertNotNil(storeDescription)
if let storeDescription = storeDescription {
XCTAssertTrue(storeDescription.shouldAddStoreAsynchronously)
}
Or if we use conditional chaining anywhere we access the optional:
let container = CoreDataContainer(name: modelName)
let storeDescription = container.persistentStoreDescriptions.first
XCTAssertEqual(storeDescription?.shouldInferMappingModelAutomatically, false)
This is an ideal job for XCTUnwrap
.
Using XCTUnwrap With A Throwing Test
Apple introduced XCTUnwrap
with Xcode 11. It takes an optional value and asserts that it’s not nil
. If the assertion succeeds it returns the unwrapped value. If the assertion fails it throws an exception. This makes it ideal to use with a throwing test method.
Let’s rewrite our test using XCTUnwrap
and a throwing test method. When we try
to unwrap the optional value we either get back a description or the test fails with an exception:
func testShouldAddStoreAsynchronously() throws {
let container = CoreDataContainer(name: modelName)
let storeDescription = try XCTUnwrap(container.persistentStoreDescriptions.first)
XCTAssertTrue(storeDescription.shouldAddStoreAsynchronously)
}
This is almost as concise as force unwrapping but without the disadvantage of crashing when it goes wrong. It gives us an unwrapped value that we can access in the rest of the text without optional chaining. As an added bonus, we also get a useful failure message:
Next time you find yourself jumping through hoops to unwrap optionals in your tests consider using XCTUnwrap
.