Unit Testing Code That Throws Error in Swift

There are a few different ways to Unit test code that throws an Error in Swift and my preferred way is to use the XCTAssertThrowError and XCTAssertNoThrows. I will also share with you how to use the traditional way of handling an error with the do-try-catch.

If you are interested in video lessons on how to write Unit tests and UI tests to test your Swift mobile app, check out this page: Unit Testing Swift Mobile App

Error To Be Thrown

Let’s assume that we have the following custom error that will be thrown by our Swift function under test.

enum SignupError: Error {
    case illigalCharactersFound
}

Function Under Test That Throws Error

As an example, I have created a simple Validator class with a single function that will throw an error if the provided value of user’s first name contains an illigal character. This is a very much simplified code, so do not be very critical about it.

class SignupFormModelValidator  {
    private let invalidCharacters = CharacterSet(charactersIn: "{}$#%*&^.,/?!@")
    
    func isFirstNameValid(_ firstName: String) throws -> Bool {
        var returnValue = true
        
        // Check for illigal characters
        if ( firstName.rangeOfCharacter(from: invalidCharacters) != nil ) {
            throw SignupError.illigalCharactersFound
        }
        
        // Check first name
        if firstName.count < 2 || firstName.count > 10 {
            returnValue = false
        }
        
        return returnValue
    }
    
}

If any of the illegal characters listed in the CharacterSet will be found in the user’s first name, then a SignupError.illigalCharactersFound error will be thrown.

XCTAssertThrowsError Example

My preferred way of testing if the function under test throws an error is to use the XCTAssertThrowsError test assertion. Below is an example of a unit test method, that expects a specific error to be thrown. If an error is not thrown or a different error is thrown, the test method will fail.

func testFirstNameValidation_WhenInvalidCharactersProvided_ThrowsAnError() {
    // Arrange
    let sut = SignupFormModelValidator()
    
    // Act and Assert
    XCTAssertThrowsError(try sut.isFirstNameValid("Sergey*"), "The isFirstNameValid() should have thrown an error if user's first name contains illigal characters") { (errorThrown) in

        XCTAssertEqual(errorThrown as? SignupError, SignupError.illigalCharactersFound)

    }
}

The code snippet above checks of the function under test throws a specific error. To check if the function under test throws any error at all, I could use a short version of XCTAssertThrowsError.

XCTAssertThrowsError(try sut.isFirstNameValid("Sergey*"))

XCTAssertNoThrow Example

To assert if the function under test does not though an error and to fail the test if any kind of error is thrown, we can use the XCTAssertNoThrow test assertion.

func testFistNameValidation_WhenValidCharactersProvided_ThrowsNoErrors() {
    // Assert
    let sut = SignupFormModelValidator()
    
    // Act and Assert
    XCTAssertNoThrow(try sut.isFirstNameValid("Sergey"), "The isFirstNameValid() should not have thrown an error when there are no illigal characters provided")
 
}

A very short and very easy to read and understand code.

If not using the XCTAssertThrowsError and the XCTAssertNoThrow test assertions we can still handle errors the traditional way with the do-try-catch block and then fail the test method with the XCTFail() unconditional test assertion. Let’s see how it works.

Do-Try-Catch Instead of XCTAssertThrowsError

The below unit test method will call a function under test that throws an error and will handle the thrown error with the do-try-catch block instead of the XCTAssertThrowsError.

func testFirstNameValidation_WhenInvalidCharactersProvided_ThrowsAnError() {
    // Arrange
    let sut = SignupFormModelValidator()
    
   // Act and Assert
    do {
       let _ = try sut.isFirstNameValid("Sergey*")
       XCTFail("The isFirstNameValid() was supposed to throw an error when illigal characters used in User's first name")
    } catch SignupError.illigalCharactersFound {
        // Successfully passing
        return
    } catch {
         XCTFail("The isFirstNameValid() was supposed to throw the SignupError.illigalCharactersFound Error when illigal characters used. A different Error was thrown.")
        return
    }
    
    
}

If a call to isFirstNameValid(“Sergey*”) method does not throw an error, and method execution continues to the next line, then we will fail the test method with the XCTFail(). If a different error than the SignupError.illigalCharactersFound is thrown, we will also fail the test method with the XCTFail(). The test method will work well, although as you can see the code is much longer and harder to read than if we were using the XCTAssertThrowsError.

Do-Try-Catch Instead of XCTAssertNoThrow

To test if the function under test does not throw an error, the below unit test method will use the do-try-catch block instead of XCTAssertNoThrow.

func testFistNameValidation_WhenValidCharactersProvided_ThrowsNoErrors() {
    // Assert
    let sut = SignupFormModelValidator()
    
    // Act and Assert
    do {
    let _ = try sut.isFirstNameValid("Sergey")
    } catch {
        XCTFail("The isFirstNameValid() was not supposed to throw an Error when a valid First Name value was provided")
    }
}

If function under test called isFirstNameValid(“Sergey”) throws an error, then the XCTFail() will fail the test method.

I hope this tutorial was of some value to you.

If you are interested to learn more about how to do Unit testing and UI Testing of Swift mobile apps, then have a look at my video lessons here – “Unit Testing Swift Mobile App“.

Happy Unit Testing! 🙋🏻‍♂️

Leave a Reply

Your email address will not be published. Required fields are marked *