Swift-testing: what's the recommend approach to test throwing function?

In swift-test vision document the author explained the rational behind simple expectation API decision. That makes sense to me but I find it inconvenient without a built-in API to test error. Below is an example.

Setup: I'd like to test func foo() throws an expected error.

struct FooError: Error {
}

func foo() throws {
    throw FooError()
}

My test code:

struct MyTest {
    @Test("FooError")
    func fooError() {
        var expectedError: FooError? = nil
        do {
            try foo()
        } catch {
            expectedError = error as? FooError
        }
        #expect(expectedError != nil)
    }
}

I wonder if there is better way to do it? While the code is simple, copying and pasting it in every test seems unacceptable. I'm thinking to move them to a function. But if every user defines a custom function like this, doesn't it mean the library should provide one out-of-box? Am I missing something? Thanks.

You can use #expect(throws:), which comes in a few varieties:

How can I test that any error was thrown?

#expect(throws: (any Error).self) {
  try foo()
}

How can I test that an error of a specific type was thrown?

#expect(throws: FooError.self) {
  try foo()
}

How can I test that an exact error was thrown?

#expect(throws: FooError.insufficientWidgets) {
  try foo()
}

Note that this case is available only if your error type conforms to Equatable.

How can I test that an error was thrown with more complex requirements?

#expect {
  try foo()
} throws: { error in
  guard let error = error as? FooError else {
    return false
  }
  return error.widgetCount > 123 && error.isFunny
}

How can I test that no error was thrown?

Either call the throwing function directly—and if an error is thrown, allow the test to fail—or, if you want to emphasize the semantics of the function not throwing, use Never with #expect(throws:):

#expect(throws: Never.self) {
  try foo()
}
7 Likes

Thanks! That's what I was looking for. Just FYI, this API's document isn't linked in the library's main page here.