How to unit test throwing functions in Swift?

前端 未结 5 577
暗喜
暗喜 2021-02-01 12:56

How to test wether a function in Swift 2.0 throws or not? How to assert that the correct ErrorType is thrown?

相关标签:
5条回答
  • 2021-02-01 13:22

    Swift 4.1 Error throwing Test for associated values

    enum ParseError: Error, Equatable {
        case unexpectedArgument(String)
    }
    
    func testWithNoSchemaButWithOneArgument() {
        XCTAssertThrowsError(try Args(withSchema: "", andArguments: ["-x"])) { error in
            XCTAssertEqual(error as? ParseError, ParseError.unexpectedArgument("Argument(s) -x unexpected."))
        }
    }
    
    0 讨论(0)
  • 2021-02-01 13:26

    Given the following functions and declarations:

    enum SomeError: ErrorType {
        case FifthError
        case FirstError
    }
    
    func throwingFunction(x: Int) throws {
        switch x {
        case 1:
            throw SomeError.FirstError
        case 5:
            throw SomeError.FifthError
        default:
            return
        }
    }
    

    This function will throw a FifthError if 5 is given to the function and FirstError if 1 is given.

    To test, that a function successfully runs the unit test could look as follows:

    func testNotError() {
        guard let _ = try? throwingFunction(2) else {
            XCTFail("Error thrown")
            return
        }
    }
    

    The let _ may also be replaced by any other name, so you can further test the output.

    To assert that a function throws, no matter what ErrorType the unit test could look like this:

    func testError() {
        if let _ = try? throwingFunction(5) {
            XCTFail("No error thrown")
            return
        }
    }
    

    If you want to test for a specific ErrorType it's done with a do-catch-statement. This is not the best way compared to other languages.

    You have to make sure that you...

    • return in the catch for the correct ErrorType
    • XCTFail() and return for all other catch
    • XCTFail() if no catch is executed

    Given this requirements a test case could look like this:

    func testFifthError() {
        do {
            try throwingFunction(5)
        } catch SomeError.FifthError {
            return
        } catch {
            XCTFail("Wrong error thrown")
            return
        }
        XCTFail("No error thrown")
    }
    
    0 讨论(0)
  • 2021-02-01 13:28

    You can use this function:

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

    Example:

    XCTAssertThrowsError(try funcThatThrowsSpecificError(), error: SpecificErrorEnum.someError)
    
    0 讨论(0)
  • 2021-02-01 13:29

    At least of Xcode 7.3 (maybe earlier) you could use built-in XCTAssertThrowsError():

    XCTAssertThrowsError(try methodThatThrows())
    

    If nothing is thrown during test you'll see something like this:

    If you want to check if thrown error is of some concrete type, you could use errorHandler parameter of XCTAssertThrowsError():

    enum Error: ErrorType {
        case SomeExpectedError
        case SomeUnexpectedError
    }
    
    func functionThatThrows() throws {
        throw Error.SomeExpectedError
    }
    
    XCTAssertThrowsError(try functionThatThrows(), "some message") { (error) in
        XCTAssertEqual(error as? Error, Error.SomeExpectedError)
    }
    
    0 讨论(0)
  • 2021-02-01 13:36

    EDIT: Updated the code for Swift 4.1 (still valid with Swift 5.2)

    Here's the latest Swift version of Fyodor Volchyok's answer who used XCTAssertThrowsError:

        enum MyError: Error {
            case someExpectedError
            case someUnexpectedError
        }
    
        func functionThatThrows() throws {
            throw MyError.someExpectedError
        }
    
        func testFunctionThatThrows() {
            XCTAssertThrowsError(try functionThatThrows()) { error in
                XCTAssertEqual(error as! MyError, MyError.someExpectedError)
            }
        }
    

    If your Error enum has associated values, you can either have your Error enum conform to Equatable, or use the if case statement:

        enum MyError: Error, Equatable {
            case someExpectedError
            case someUnexpectedError
            case associatedValueError(value: Int)
        }
    
        func functionThatThrows() throws {
            throw MyError.associatedValueError(value: 10)
        }
    
        // Equatable pattern: simplest solution if you have a simple associated value that can be tested inside 1 XCTAssertEqual
        func testFunctionThatThrows() {
            XCTAssertThrowsError(try functionThatThrows()) { error in
                XCTAssertEqual(error as! MyError, MyError.associatedValueError(value: 10))
            }
        }
    
        // if case pattern: useful if you have one or more associated values more or less complex (struct, classes...)
        func testFunctionThatThrows() {
            XCTAssertThrowsError(try functionThatThrows()) { error in
                guard case MyError.associatedValueError(let value) = error else {
                    return XCTFail()
                }
    
                XCTAssertEqual(value, 10)
                // if you have several values or if they require more complex tests, you can do it here
            }
        }
    
    0 讨论(0)
提交回复
热议问题