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.
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.
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 since
ViewModeluses 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
ViewModel uses the method
fetchUserName() of its interactor. The important thing to understand here is that we want to test only if
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
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
Now, we can change the
ViewModel constructor to accept the new protocol as dependency:
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
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
_ = 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.
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
With this implementation, we wouldn’t be able to inject any dependencies. For this scenario, we can use a
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
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
Interactor.self returns the type of
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
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
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:
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.
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
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
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
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
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
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.