Swift-testing and Errors with associated types

There are several missing features which make your code have to look terrible:

  1. is case
  2. An overload of #expect which takes a () -> Bool instead of a Bool.
  3. Swift Testing's lack of incorporation of typed throws.*
@Suite("Test Errors")
struct TestingErrors {
  enum ExampleError: Error & Equatable {
    case unknownError(_ message: String)
    case codedError(_ code:Int)
    case noExtras
  }

  func `throw`(message: String) throws(ExampleError) {
    throw .unknownError(message)
  }

  func `throw`(code: Int) throws(ExampleError) {
    throw .codedError(code)
  }

  func `throw`() throws(ExampleError) {
    throw .noExtras
  }

  @Test func simpleErrorTest() throws {
    #expect(throws: ExampleError.noExtras, performing: `throw`)
  }

  @Test func testForExpectedCode() throws {
    let expectedCode = 42

    #expect(throws: ExampleError.codedError(expectedCode)) {
      try `throw`(code: expectedCode)
    }

    #expect({
      guard case .codedError(let code) = (#expect(throws: ExampleError.self) {
        try `throw`(code: expectedCode)
      }) else { return false }
      return (20...49).contains(code)
    } ())
  }

  @Test func testForAnyCode() throws {
    #expect({
      guard case .codedError = (#expect(throws: ExampleError.self) {
        try `throw`(code: .random(in: 0...5))
      }) else { return false }
      return true
    }() )
  }

  @Test func testForMessage() throws {
    #expect({
      guard case .unknownError(let message) = (#expect(throws: ExampleError.self) {
        try `throw`(message: "well that was a big oops")
      }) else { return false }
      return message.contains("oops")
    } ())
  }
}

* This is the least important; typed throws feel like they're another abandoned feature at this point, and require redundant annotation which is worse than providing a metatype.

expect {
  guard case .unknownError(let message) = (expectThrows { () throws(ExampleError) in
    try `throw`(message: "well that was a big oops")
  }) else { return false }
  return message.contains("oops")
}
func expectThrows<Error>(performing expression: () throws(Error) -> some Any) -> Error? {
  #expect(throws: Error.self, performing: expression)
}

func expect(_ condition: () -> Bool) {
  #expect(condition())
}
1 Like