How To Test fatalError In Swift
Are you using fatalError functions to enforce the flow of your application and you’re keen to test it? Let’s find out a safe way to do it.
Swift has several ways to manage failures. One of them is the function
fatalError. Since we should cover our production code with tests as much as possible, we need also a way to test the
fatalError. By default, there are no ways to handle it. If we test a function with a
fatalError, our tests would crash.
In this article, we are going to see a safe way to test if a
fatalError is called in our production code.
By default, we cannot test if a method calls a
fatalError since we don’t have ways to wrap the failure and the tests would crash. For this reason, we need a way to handle the
fatalError function to prevent any crashes in our tests. To achieve it, we should write a custom
If we have a look at the Swift interface for the function
fatalError, we would find this:
Our goal is replacing this function with a custom one. Therefore, we must declare a new function with the same signature in our application. The important thing is declaring it at the top-level. It means that it mustn’t be inside any classes/structs/enums.
We can start creating a new file called
FatalErrorUtil.swift. Then, at the top-level of this file we can add our new
With this new method, every time we use
fatalError in our code, the compiler will use this function instead of the default one of Swift.
At this point, we need to add an implementation. The strategy for this implementation is using the default Swift
fatalError implementation for the production code and a custom one for unit test.
We can use a struct
FatalErrorUtil which will provide the implementation of
- Closure which provides the implementation of
fatalError. By default, it uses the one provided by Swift.
fatalErrorimplementation provided by Swift.
- Static method to replace the
fatalErrorimplementation with a custom one. We’ll see later how to use it for unit tests.
- Restores the
fatalErrorimplementation with the default one. We’ll need it later for the unit tests.
The next step is using this struct in our custom
So far so good. If we use
fatalError in our production code, we can expect the same behaviour of the one provided by Swift.
Unit test handling
In the previous section, we introduced a new
fatalError implementation. Now, we are able to handle it for the unit tests.
We can start adding a new file
XCTestCase+FatalError.swift in the project target of our unit tests to extend
The target membership of the file should be something similar to this:
XCTestCase extension, we can add a method
expectFatalError which will wrap the
With this method, we can wrap
fatalError and test if the method under test calls a
fatalError function with an expected message. We’ll see later how to use it.
Inside this method, we must replace the default
fatalError implementation with a mock to test if it’s called with an expected message:
If the compiler doesn’t find
FatalErrorUtil.replaceFatalError, it means that you must add
@testable import <ProjectName> at the beginning of the file.
expectation because it’s an asynchronous test.
As we may have noticed in the example above, we call a method
unreachable which is this one:
fatalError is a function which returns
Never. It means that this function will never finish its execution since will be stopped by either a failure or a thrown error. In this case, a normal
fatalError would never complete since it lets crash the application. Therefore, we must simulate a never returning function. We can do it with an infinite loop—like in method
unreachable. If we use the approach of an infinite loop, we should call
RunLoop.current.run() to let execute any services attached to the main thread. You can read more details about this method here.
Then, we must execute the
testcase closure in a background thread, since the main one will be blocked by the infinite loop:
Finally, we must handle the
expectation completion to test the
fatalError message and restore the default
We’ve just finished the implementation of our
XCTestCase extension and it should be like this:
We’ve completed everything to test our
Now, we can see a plain example to understand how to use the new method
Let’s consider the following
We call a
fatalError if the argument of
handle is less than 6.
We can test this method like this:
This test would fail if either the method
handle doesn’t call the
expectedMessage is not the same message of the
In this article, we focused on the
fatalError but we can use a similar approach also for other failure methods like
preconditionFailure. You can have a look here for more details.