How to test method that throws error in Swift 2?

2019-04-05 04:49发布

问题:

This is my method definition:

func isValidForMode(mode: DBFindViewControllerMode) throws -> Bool { }

Now I can test this in simple way, since I know that it DO NOT throws an error:

XCTAssertTrue(try! searchOptionsManager.isValidForMode(.Address))

But what if I know that method throws?

The best solution would be XCTAssertThrows(), but it is not:-)

Below is my try:

do {
    try searchOptionsManager.isValidForMode(.Address)
} catch let error {
    XCTAssertEqual(error as! DBErrorType, DBErrorType.CannotBeEmpty("Street"))
}

But it fails, because:

Cannot find an overload for XCTAssertEqual that accepts an argument list of type (DBErrorType, DBErrorType)

回答1:

Make your DBError conforming to Equatable:

enum DBError: ErrorType, Equatable {
  case CannotBeEmpty(message: String)
}

func ==(lhs: DBError, rhs: DBError) -> Bool {
  switch (lhs, rhs) {
    case (.CannotBeEmpty(let leftMessage), .CannotBeEmpty(let rightMessage)):
      return leftMessage == rightMessage
  }
}

And then you can use it in XCTAssertEqual:

func testExample() {
  do {
    try isValid()
  }
  catch let e as DBError {
    XCTAssertEqual(e, DBError.CannotBeEmpty(message: "Street"))
  }
  catch {
    XCTFail("Wrong error")
  }
}

Or create your own XCTAssertThrows.

enum DBError: ErrorType, Equatable {
  case CannotBeEmpty(message: String)
}

func ==(lhs: DBError, rhs: DBError) -> Bool {
  switch (lhs, rhs) {
    case (.CannotBeEmpty(let leftMessage), .CannotBeEmpty(let rightMessage)):
      return leftMessage == rightMessage
  }
}

And:

func XCTAssertThrows<T: ErrorType where T: Equatable>(error: T, block: () throws -> ()) {
  do {
    try block()
  }
  catch let e as T {
    XCTAssertEqual(e, error)
  }
  catch {
    XCTFail("Wrong error")
  }
}

class TestsTests: XCTestCase {

    func testExample() {
      XCTAssertThrows(DBError.CannotBeEmpty(message: "Street")) { try isValid() }
    }

}


回答2:

Or simply use an optional try:

extension XCTestCase {
    func XCTAssertThrows(@autoclosure expression: () throws -> Void, _ message: String = "", file: String = __FILE__, line: UInt = __LINE__) {
        XCTAssert((try? expression()) == nil, message, file: file, line: line)
    }
}

No need to conform to Equatable



回答3:

The best solution so far I have found is:

do {
    try searchOptionsManager.isValidForMode(.Address)
    XCTAssertTrue(false)
} catch {
    XCTAssertTrue(true)
}

This way you can test if exception is really thrown, but you cannot check what type of exception is thrown.



回答4:

Here is @robertvojta answer with several modification for Xcode 9 and Swift 3 - 4:

extension XCTestCase {
    func XCTAssertThrows<ErrorType: Error, T>(expression: @autoclosure () throws -> T, error: ErrorType) where ErrorType: Equatable {
        do {
            _ = try expression()
        } catch let caughtError as ErrorType {
            XCTAssertEqual(caughtError, error)
        } catch {
            XCTFail("Wrong error")
        }
    }
}

Usage:

enum APIError: LocalizedError {
    case cancelled

    public var errorDescription: String? {
        switch self {
        case .cancelled:
            return "The operation has been cancelled by user."
        }
    }
}

func testThatIsThrowsCancelledByUserError() {
    XCTAssertThrows(expression: try api.cancelLoginOperation(), error: APIError.cancelled)
}


回答5:

If you know that the function throws an error then you should also ensure that you fail in the event that an error is not thrown.

I'm modifying answers from @robertvojta and @vadim-bulavin here:

extension XCTestCase {
    func XCTAssertThrows<ErrorType: Error, T>(expression: @autoclosure () throws -> T, error: ErrorType) where ErrorType: Equatable {
        do {
            _ = try expression()
            XCTFail("No error thrown")
        } catch let caughtError as ErrorType {
            XCTAssertEqual(caughtError, error)
        } catch {
            XCTFail("Wrong error")
        }
    }
}

Usage:

enum APIError: LocalizedError {
    case cancelled

    public var errorDescription: String? {
        switch self {
        case .cancelled:
            return "The operation has been cancelled by user."
        }
    }
}

func testThatIsThrowsCancelledByUserError() {
    XCTAssertThrows(expression: try api.cancelLoginOperation(), error: APIError.cancelled)
}


回答6:

Here is the example that you will understand try-catch check this below code

func validateCredencials() throws {

guard username.characters.count > 0  && password.characters.count > 0 
else { throw EncryptionType.Empty }
    guard password.characters.count >= 5 else { throw EncryptionType.Short }
}
    do {
    try validateCredencials()
    }catch EncryptionType.Empty {
        print("password Empty")

    } catch EncryptionType.Short {
        print("Password too shoot")
    }catch {
        print("Some thing went Wrong")
    }

Hope you Understand