XCTAssertThrows与自定义errorHandler的奇怪行为

时间:2016-03-23 22:46:15

标签: swift error-handling syntax-error closures

在我的单元测试中,我有以下代码来检查抛出的错误是否是预期类型。它由两个相同的语句完成,其中一个语句没有编译:

    enum Error: ErrorType {
        case SomeExpectedError
        case SomeUnexpectedError
    }

    func functionThatThrows() throws {
        throw Error.SomeExpectedError
    }

    // 1) this compiles fine
    XCTAssertThrowsError(try functionThatThrows()) { (error) in
        switch error {
        case Error.SomeExpectedError: break
            //everything is fine
        case Error.SomeUnexpectedError: fallthrough
        default:
            XCTFail("Unexpected error thrown")
        }
    }

    // 2) doesn't compiles at all
    XCTAssertThrowsError(try functionThatThrows()) { (error) in
        XCTAssertEqual(error as? Error, Error.SomeExpectedError)
    }

第一个语句编译并且工作正常,但是第二个语句告诉我有两个错误: Errors thrown from here are not handledCannot convert value of type '(Error) -> Void' to expected argument type 'String'

这段代码有什么问题?错误消息的含义是什么?

我正在使用Xcode 7.3。

2 个答案:

答案 0 :(得分:5)

经过一番研究后,我发现我的代码有两个问题:

1)似乎我的闭包被认为是"Impilictly returning"因为它只包含一个可执行语句

2)编译器被我的语句搞糊涂了,因为XCTAssertThows接受了另一个接受闭包的参数 - 它@autoclosure _ message: () -> String

结果编译器认为我传递了消息的闭包(恕我直言地混淆了设计决定 - 传递@autoclosure作为消息参数),当我实际上为errorHandler传递了闭包。

解决方案很简单:

    // explicitly show closure returning type Void so that compiler couldn't
    // use it as message argument (which closure must return String)
    XCTAssertThrowsError(try functionThatThrows()) { (error) -> Void in
        XCTAssertEqual(error as? Error, Error.SomeExpectedError)
    }

    // explicitly show closure's argument type
    XCTAssertThrowsError(try functionThatThrows()) { (error: ErrorType) in
        XCTAssertEqual(error as? Error, Error.SomeExpectedError)
    }

    // use "message" parameter to preserve argument order
    XCTAssertThrowsError(try functionThatThrows(), "some message") { (error) in
        XCTAssertEqual(error as? Error, Error.SomeExpectedError)
    }

    // wrap executable line in "do {}". I guess it works because closure stops
    // being "implicitly returning" and now compiler treat it as () -> Void
    // which is expected type of errorHandler argument
    XCTAssertThrowsError(try functionThatThrows()) { (error) in
        do {
            XCTAssertEqual(error as? Error, Error.SomeExpectedError)
        }
    }

答案 1 :(得分:0)

我正在使用此静态函数来针对特定异常进行测试:

func XCTAssertThrowsError<T, E: Error & Equatable>(
  _ expression: @autoclosure () throws -> T,
  error: E,
  in file: StaticString = #file,
  line: UInt = #line
  ) {
  var thrownError: Error?
  XCTAssertThrowsError(
    try expression(),
    file: file,
    line: line) {
      thrownError = $0
  }

  XCTAssertTrue(
    thrownError is E,
    "Unexpected error type: \(type(of: thrownError))",
    file: file,
    line: line
  )

  XCTAssertEqual(
    thrownError as? E,
    error,
    file: file,
    line: line
  )
}

用法示例:

  func testEmptyMrz() {
    XCTAssertThrowsError(
      try passportMrzValidator.validate(for: ""),
      error: PassportMrzValidationError.invalidMrz
    )
  }