Mock Dependencies: Instance and Metatype Injection With Swift
Do you want to mock all your dependencies to write perfect unit tests? Let’s find out a couple of approaches.
Introduction
Nowadays, unit testing is an important topic. There is no doubt that we should test our code as much as possible to avoid bugs. Unfortunately, unit testing is not always easy since we may have very complex objects with a lot of dependencies.
In this article, I want to show you a couple of approaches which I use to mock the dependencies of my objects to keep the tests as plain as possible.
Happy Reading!
What is a dependency?
When we have to test an object, we usually have two parts:
- The system under test (SUT): The object to test.
- The dependencies: Any objects used by SUT internally.
Let’s use the ViewModel
of 4V Engine for our examples:
|
|
For the sake of explanation, fetchUserName
is a synchronous method. If we have to send API requests, we should always perform asynchronous operations to avoid blocking the main queue.
If we want to test ViewModel
, we can consider:
ViewModel
: the system under test.Interactor
: a dependency sinceViewModel
uses it to get a user name.
Mock the dependencies
Why should we mock the dependencies in our unit tests?
If we don’t have a clear understanding of the answer to this question, we may risk writing wrong tests.
Unit testing is born to test the behavior of a single component—SUT—without caring of its dependencies. The purpose of mocking the dependencies is to keep the SUT isolated from external objects.
If we consider the example used in Mock the dependencies, we may want to test that the computed property userName
of ViewModel
uses the method fetchUserName()
of its interactor. The important thing to understand here is that we want to test only if ViewModel
calls fetchUserName()
. We don’t care of the actual result of fetchUserName()
. We don’t need to test if fetchUserName()
works because we’ll test it in another test suite where we have Interactor
as SUT.
The first step to mock the dependencies is finding a proper way to inject them. Once we are able to inject a dependency, we’ll be able to inject a fake dependency for testing purposes. I use mainly two approaches: Instance Injection
and Metatype Injection
.
Instance Injection
This kind of injection is the most common. We inject the instance of a dependency using a parameter of a method or constructor.
The first step to use this injection is abstracting the dependency. We can achieve it with a protocol.
We can continue using the example used in Mock the dependencies and we can abstract Interactor
with a InteractorType
protocol:
|
|
Now, we can change the ViewModel
constructor to accept the new protocol as dependency:
|
|
Since Interactor
conforms to the new protocol, we can still instantiate the ViewModel
with an Interactor
instance. We can also use a default parameter value to omit the interactor parameter like this:
|
|
At this point, we’ve just created a new instance injection of Interactor
and we are ready to use it for testing. The first step is creating a new mock interactor which conforms to InteractorType
:
|
|
If you don’t know what is a stub class, you can have a look here.
Now, we are finally ready to inject a mock dependency to test our SUT and its userName
property.
|
|
We use _ =
to discard the value returned by sut.userName
. We don’t care of the value but just if we call fetchUserName
in the computed property.
Metatype Injection
Instance injection doesn’t suit every situations. We may have some cases where we don’t want to inject the instance of a dependency.
The strategy of Metatype Injection
is using the dependency types which we want to use. In this way, we don’t need an instance but just the type of dependency.
We can continue using the example used in Instance Injection. We’ll see a real scenario at the end of this section.
Let’s consider that we no longer want to inject an instance of Interactor
but we want to instantiate an Interactor
object inside the ViewModel
constructor:
|
|
With this implementation, we wouldn’t be able to inject any dependencies. For this scenario, we can use a Metatype Injection
.
First of all, we can create a Configuration
struct inside our ViewModel
with the dependency types to use:
|
|
By default, we set Interactor.self
as interactor dependency type to omit it when we instantiate Configuration
.
If you don’t understand the meaning of:
|
|
InteractorType.Type
means that the property interactorType
will not store an instance but just a metatype of InteractorType
. Interactor.self
returns the type of Interactor
.
Swift allows us to instantiate a new object using its type as variable. The only constraint is that we have to use the method init()
explicitly. It means that the following code would throw a compiler error:
|
|
Instead, we should write:
|
|
In our example, interactorType
is a property of type InteractorType.Type
to keep the type abstract. Therefore, we wouldn’t able to call its init
method since the protocol doesn’t have it. We can solve this problem adding a init
method in the protocol like this:
|
|
Then, we must add the init
method also in Interactor
, which implements the protocol:
|
|
Remember to use the keyword required
before the init
to indicate that every subclasses of Interactor
must implement that constructor.
The last step is injecting this configuration in the ViewModel
constructor:
|
|
We use Configuration()
as default parameter to omit it when we create a ViewModel
object. In this way, the default interactorType
value is Interactor.self
. We’ll see in the tests how to inject a mock dependency.
We’ve just completed the implementation. Now, we can focus on the tests.
With this approach, the tests are a little bit tricky. With Instance Injection
, we can inject an instance of StubInteractor
. With Configuration Injection
, we cannot inject an instance but just a type. For this reason, when we write the tests, we must use static properties to check our test results.
We must use static because a static property stores an information without using an instance and its lifetime is the entire run of the application.
We can refactor StubInteractor
to use the static information:
|
|
The method clean
is very important to clean all the static information used in the tests. Since we are using static properties, our application shares the value of isFetchUserNameCalled
everywhere—like a Singleton. Therefore, If we don’t clean isFetchUserNameCalled
, it would remain true
also in the next tests.
Now, we can set our test suite like this:
|
|
Remember to call the clean
method in the tearDown
to clean all the static information for the next tests.
If StubInteractor
has a lot of properties to store the test results, the method clean
would have a lot of properties to clean. We can avoid it using a Singleton for StubInteractor
.
We can restore isFetchUserNameCalled
from static to a normal property. Then, we must add a new static property:
|
|
which will be the access point of our singleton object. Then, we set the shared
value internally in the constructor:
|
|
And, finally, we can clean our singleton in our clean
method:
|
|
The final version of StubInteractor
should be like this:
|
|
Thanks to this refactor, in our test assert
, we can use our new Singleton to test the value of isFetchUserNameCalled
:
|
|
This is the Metatype Injection
. You may argue that it’s complex and tricky with a Singleton. I agree that we should go with Instance Inject
as much as possible but, unfortunately, I’ve found some situations where with a configuration the code would remain cleaner.
An example can be the router example of 4V Engine:
|
|
Here, we have an implementation of a delegate—UsersListNavigationDelegate
—where we create a new UserDetailsViewPresenter
object using a user and a navigation delegate as parameters. This object would be quite difficult to inject from outside keeping the code clean. In this case, a Metatype Injection
would be much better.
Another real scenario is the Core Data stack. If we have a class which manages a Core Data implementation, we cannot inject the instances of the stack objects from outside in a clean way since they are tightly coupled. You can see how to inject the Core Data stack with Metatype Injection
in the StorageKit
class CoreDataStorage
.
Conclusion
I used a Configuration
struct for Metatype Injection
to keep clean the approach. Of course, we can use the same Configuration
struct approach also for Instance Injection
.
I usually try using Instance Injection
as much as possible to avoid using static variables in my unit tests. I created Metatype Injection
to solve some injection problems after trying different approaches. If you have better approaches or you want to share your knowledge, please leave a comment, it would be greatly appreciated. Thank you.