Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test method that throws error in Swift 2?

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)

like image 503
Bartłomiej Semańczyk Avatar asked Jul 22 '15 09:07

Bartłomiej Semańczyk


People also ask

How do you catch an error in Swift?

There are four ways to handle errors in Swift. You can propagate the error from a function to the code that calls that function, handle the error using a do - catch statement, handle the error as an optional value, or assert that the error will not occur.

Do catch vs try catch Swift?

do – This keyword starts the block of code that contains the method that can potentially throw an error. try – You must use this keyword in front of the method that throws. Think of it like this: “You're trying to execute the method.

How does Swift handle fatal errors?

According to Swift Error Handling Rationale, the general advice is: Logical error indicates that a programmer has made a mistake. It should be handled by fixing the code, not by recovering from it. Swift offers several APIs for this: assert() , precondition() and fatalError() .


5 Answers

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)
}
like image 162
satbot Avatar answered Nov 10 '22 23:11

satbot


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

}
like image 22
zrzka Avatar answered Nov 11 '22 00:11

zrzka


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

like image 37
awph Avatar answered Nov 11 '22 01:11

awph


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.

like image 25
Bartłomiej Semańczyk Avatar answered Nov 11 '22 00:11

Bartłomiej Semańczyk


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)
}
like image 32
Vadim Bulavin Avatar answered Nov 11 '22 01:11

Vadim Bulavin