XCTUnwrap Optionals In Your Tests

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!

Force unwrapping is concise but has the disadvantage of crashing and aborting our test run when something goes wrong:

Fatal error: Unexpectedly found nil while unwrapping an Optional value

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
if let storeDescription = storeDescription {

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)

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:

XCTUnwrap failed: excepted non-nil value of type NSPersistentStoreDescription

Next time you find yourself jumping through hoops to unwrap optionals in your tests consider using XCTUnwrap.

See Also