Stubbing in pair with Swift compiler
How to quickly write unit test stub in Swift without code generation
Often we need to unit test a code with side-effects that interact with other parts of your system by abstraction— protocols in Swift. There are many ways to verify if it works as expected: stubbing, spying, dummying etc. In general, engineers dubbed these forms as mocks, even “test double” is a legit name for that.
FYI, “test double” name comes from “stunt double” in the film industry.
Writing test doubles (like stubs or mocks) is a boring process in Swift and developers often try to generate it automatically by using tools like Sourcery/SwiftyMocky. However, if you wish to have more granular control or don’t want to introduce a new step to the build process, there is one trick to quickly (at least quicker) implement a test double.
Side note: stubbing/mocking difference
If you know how stubbing and mocking works, you can easily skip the following two chapters and jump to the next chapter or directly to the solution.
First, let’s distinguish what is the difference between stubs and mocks as they are quite similar. The fundamental difference sits in a verification process:
- for stubs, a test case has to manually call some methods to verify that expected side-effect happened
- mocks make verification automatically using predefined configuration
Let’s compare it in a pseudo-swift code:
CounterMock is preconfigured during initialization contrary to
CounterStub where at the verification step we need to explicitly compare some
addCalledTimes property to 3. The way these two test doubles are implemented is an implementation detail. The general rule of thumb is that verification distinguishes if we are using a stub or a mock (or mixed hybrid).
Stubbing a function
Let’s leave theoretical discussion off and go back to the ground. How actually a stub can be implemented in Swift? We want to create an object that conforms to a protocol with a chance to 1) verify if given functions have been called with the expected arguments and 2) control functions return values.
There are many approaches to do that, and the simplest is to introduce as many properties as behaviors we want to track into a stub. E.g. if you want to very that function has been called given times, add a counter, increment it in a function and after the test verify its value — as presented in the snippet above.
However, an alternative is to add an additional variable that provides a function placeholder (with almost the same signature type as the function) and call it when conforming to the protocol. I wrote almost because we will make it optional just for convenience if a test case completely wants to ignore a given function.
In this blog post, we will talk about protocol functions since variables are relatively simple to stub — just by introducing an instance.
For the sake of this post, let’s think about the simplest protocol that synchronously saves a username to a database and returns a boolean to inform if a procedure succeeded or not. Its mock would look like that:
For a function
addUser, we introduced a variable
addUserAction — a function that is evaluated every time
addUser is called. Then, in a test scenario you can 1) verify that your system under test (testing code) indeed called that function and 2) emulate how database reacts (whether return
Generally speaking, by introducing
addUserActionvariable that matches functions arguments and return types, we let a test case to define a body of the relevant function of a protocol.
By the way, if you don’t like optionality of
addUserAction property you may continue with an equivalent implementation:
Solution: inferred type
In the stub presented above, we had to explicitly provide a type of
addUserAction variable. For short functions, it seems to be an easy task but for ones that involve several arguments, closures,
@autoclosure it may end up with a fight between you and the compiler. To overcome such burdensomeness, we can leverage Swift’s type inference system. The solution involves a global function that returns
nil value of the same type as an argument:
At first, it looks like a nonsense function but we will use it only to pull out
T type, not its value.
Functions are first-class citizens in Swift, so we can pass a function as an argument to
asNil to get a
nil instance where
Wrapped type matches the function type. In the example below, we have a variable
addUserAction initialized as a
nil value of the same type as our protocol’s function. That is exactly what previously we had to write down manually:
It is required to mark this variable a
lazy because referencing
addUser implicitly depends on
self, which cannot be accessed before full initialization.
Database protocol is very simple so the benefit is not so spectacular, but for extremely complicated function, the gain is significant. Like here:
Another benefit of this technique is that in case of protocol’s type modification we don’t have to touch stub’s implementation at all — the compiler will automatically update types.
We developers love concentrating on a real problem rather than spending time on boring and dummy tasks like implementing test doubles. Fortunately, Swift’s static type analyzer provides a handful tool — we can leverage it to auto-infer property type used in unit test stub implementation. Then, in a matter of seconds, we can have a stub ready to work, flexible and fully customizable in a test case.
I’d like to thank Mateusz Maćkowiak for inspiring me to write this post.
Edit (16th of November):
Some people wrote me that described approach has a downside: unit test does not explicitly verify which side effects are made and furthermore it requires to provide a fallback return value when a test case did not specify concrete
xxxAction implementation (the
?? false part).
I agree, both arguments are true so if you share these concerns, let me present an alternative approach:
asNil function, we will replace it with
stub(of:), which behaves like implicitly unwrapped
Starting with Swift 4.1,
ImplicitlyUnwrappedOptionalhas been reimplemented but if you still remember it, you can think of
Unit test crashes in runtime if you call
addUser(name:) before initialising
addUserAction in a test case. As a benefit, 1) you have full control which side effects are allowed to be called (any non expected call to a stub crashes/fails a test) and 2) you don’t need to specify fallback return values.
Now, you have a choice. If you prefer to:
- never crash in unit tests -> use
- have full control of side effects -> use