Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

XCTAssertThrowsError strange behavior with custom errorHandler

In my unit test I have the following code that checks whether thrown error is of expected type. It is done with two identical statements one of which doesn't compile:

    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)
    }

First statement compiles and works fine, but seconds one says me that there are two errors: Errors thrown from here are not handled and Cannot convert value of type '(Error) -> Void' to expected argument type 'String'.

What could be wrong with this code? What do error messages mean?

I am using Xcode 7.3 .

like image 814
Fyodor Volchyok Avatar asked Mar 23 '16 22:03

Fyodor Volchyok


2 Answers

After a little research I found 2 problems with my code:

1) seems like my closure considered "Impilictly returning" because it contains only one executable statement

2) compiler was confused by my statement since XCTAssertThows accepts one more parameter that accepts closure - it's @autoclosure _ message: () -> String

As a result compiler thought that I passed closure for message (IMHO confusing design decision - to pass @autoclosure as message argument), when I actually passed closure for errorHandler.

Solutions are simple:

    // 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)
        }
    }
like image 80
Fyodor Volchyok Avatar answered Sep 16 '22 11:09

Fyodor Volchyok


I'm using this static function to test against specific exception:

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
  )
}

Usage example:

  func testEmptyMrz() {
    XCTAssertThrowsError(
      try passportMrzValidator.validate(for: ""),
      error: PassportMrzValidationError.invalidMrz
    )
  }
like image 30
Vitalii Gozhenko Avatar answered Sep 16 '22 11:09

Vitalii Gozhenko