Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unit test that verifies runtime error is thrown in Swift

Googling has led me to general Swift error handling links, but I have a more specific question. I think the answer here is "no, you're out of luck" but I want to double check to see if I'm missing something. This question from a few years ago seems similar and has some answers with gross looking workarounds... I'm looking to see if the latest version of Swift makes something more elegant possible.

Situation: I have a function which is NOT marked with throws, and uses try!.

Goal: I want to create a unit test which verifies that, yep, giving this function the wrong thing will in fact fail and throw a (runtime/fatal) error.

Problems:

  1. When I wrap this function in a do-catch, the compiler warns me that the catch block is unreachable.
  2. When I run the test and pass in the bad arguments, the do-catch does NOT catch the error.
  3. XCTAssertThrows also does not catch the error.

This function is built to have an identical signature to another, silently failing function, which I swap it out for this one on simulators so that I can loudly fail during testing (either automated or manual). So I can't just change this to a throwing function, because then the other function will have to be marked as throwing and I want it to fail silently.

So, is there a way to throw an unhandled error that I can catch in a unit test?

Alternatively, can I make this function blow up in a testable way without changing the signature?

like image 917
Elliot Schrock Avatar asked Jul 07 '20 17:07

Elliot Schrock


People also ask

What are the types of errors in unit testing?

Unit testing considerations What errors are commonly found during Unit Testing? (1) Misunderstood or incorrect arithmetic precedence, (2) Mixed mode operations, (3) Incorrect initialization, (4) Precision inaccuracy, (5) Incorrect symbolic representation of an expression.

How do I run a unit test case in Swift?

To create new unit case in iOS, go to File -> New -> File, and then select Unit Test Case Class. Doing so creates a template just like the one you got with your project. In our case, we want to name the file to correspond with the new Pokemon-related data structures we have introduced.

What is XCTest in iOS?

Overview. Use the XCTest framework to write unit tests for your Xcode projects that integrate seamlessly with Xcode's testing workflow. Tests assert that certain conditions are satisfied during code execution, and record test failures (with optional messages) if those conditions aren't satisfied.

What is difference between unit tests and UI test in Xcode?

The really short version is that unit tests have access to the code in your app (or whatever kind of module you are building) and UI tests do not have access to the code. A unit test only tests one single class per test.


2 Answers

There is no way to catch non-throwing errors in swift and you mean that by using ! after try.

But you can refactor your code in a way you can have more control from outside of the function like this:

  1. Factor out the throwing function, so you can test it in the right way:
func throwingFunc() throws {
    let json = "catch me if you can".data(using: .utf8)!
    try JSONDecoder().decode(Int.self, from: json)
}
  1. Write a non-throwing wrapper with a custom error handler:
func nonThrowingFunc( catchHandler:((Error)->Void)? = nil ) {
    guard let handler = catchHandler else { return try! throwingFunc() }
    do {
        try throwingFunc()
    } catch {
        handler(error)
    }
}

So the handler will be called only if you are handling it:

// Test the function and faild the test if needed
nonThrowingFunc { error in
    XCTFail(error.localizedDescription)
}

And you have the crashing one:

// Crash the program
nonThrowingFunc() 

Note

! (as force) is designed for situations that you are pretty sure about the result. good examples:

  • Decoding hardcoded or static JSON
  • Force unwrapping hardcoded values
  • force try interfaces when you know what is the implementation of it at the point
  • etc.

If your function is not pure enough and may fail by passing different arguments, you should consider NOT forcing it and refactor your code to a safer version.

like image 97
Mojtaba Hosseini Avatar answered Sep 29 '22 03:09

Mojtaba Hosseini


Swift (until & including current 5.5) postulates explicitly that all errors inside non-throwing function MUST be handled inside(!). So swift by-design has no public mechanism to intervene in this process and generates run-time error.

demo

like image 34
Asperi Avatar answered Sep 29 '22 03:09

Asperi