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!
Contents
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:
class Interactor {
func fetchUserName() -> String? {
let username = // Result of an API request
return username
}
}
class ViewModel {
private let interactor: Interactor
var userName: String? {
return interactor.fetchUserName()
}
init(interactor: Interactor) {
self.interactor = interactor
}
}
let interactor = Interactor()
let viewModel = ViewModel(interactor: interactor)
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:
protocol InteractorType {
func fetchUserName() -> String?
}
class Interactor: InteractorType {
func fetchUserName() -> String? {
let username = // Result of a API request
return username
}
}
Now, we can change the ViewModel
constructor to accept the new protocol as dependency:
class ViewModel {
private let interactor: InteractorType
var userName: String? {
return interactor.fetchUserName()
}
init(interactor: InteractorType) {
self.interactor = interactor
}
}
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:
class ViewModel {
private let interactor: InteractorType
var userName: String? {
return interactor.fetchUserName()
}
init(interactor: InteractorType = Interactor()) {
self.interactor = interactor
}
}
let viewModel = ViewModel()
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
:
class StubInteractor: InteractorType {
private(set) var isFetchUserNameCalled = false
func fetchUserName() -> String? {
isFetchUserNameCalled = true
return "Test"
}
}
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.
func test() {
let stubInteractor = StubInteractor()
let sut = ViewModel(interactor: stubInteractor)
_ = sut.userName
XCTAssertTrue(stubInteractor.isFetchUserNameCalled)
}
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:
class ViewModel {
private let interactor: InteractorType
var userName: String? {
return interactor.fetchUserName()
}
init() {
self.interactor = Interactor()
}
}
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:
class ViewModel {
struct Configuration {
let interactorType: InteractorType.Type = Interactor.self
}
private let interactor: InteractorType
var userName: String? {
return interactor.fetchUserName()
}
init() {
self.interactor = Interactor()
}
}
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:
let interactorType: InteractorType.Type = Interactor.self
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:
let interactor = interactorType()
Instead, we should write:
let interactor = interactorType.init()
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:
protocol InteractorType {
init()
func fetchUserName() -> String?
}
Then, we must add the init
method also in Interactor
, which implements the protocol:
class Interactor: InteractorType {
required init() {
}
func fetchUserName() -> String? {
let username = "// Result of a API request"
return username
}
}
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:
class ViewModel {
struct Configuration {
var interactorType: InteractorType.Type = Interactor.self
}
private let interactor: InteractorType
var userName: String? {
return interactor.fetchUserName()
}
init(configuration: Configuration = Configuration()) {
self.interactor = configuration.interactorType.init()
}
}
let viewModel = ViewModel()
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:
class StubInteractor: InteractorType {
private(set) static var isFetchUserNameCalled = false
required init() {}
func fetchUserName() -> String? {
StubInteractor.isFetchUserNameCalled = true
return "Test"
}
static func clean() {
StubInteractor.isFetchUserNameCalled = false
}
}
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:
class ViewModelTests: XCTestCase {
override func tearDown() {
StubInteractor.clean()
super.tearDown()
}
func test() {
let configuration = ViewModel.Configuration(interactorType: StubInteractor.self)
let sut = ViewModel(configuration: configuration)
_ = sut.userName
XCTAssertTrue(StubInteractor.isFetchUserNameCalled)
}
}
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:
private(set) static var shared: StubInteractor!
which will be the access point of our singleton object. Then, we set the shared
value internally in the constructor:
required init() {
StubInteractor.shared = self
}
And, finally, we can clean our singleton in our clean
method:
static func clean() {
StubInteractor.shared = nil
}
The final version of StubInteractor
should be like this:
class StubInteractor: InteractorType {
private(set) static var shared: StubInteractor!
private(set) var isFetchUserNameCalled = false
required init() {
StubInteractor.shared = self
}
func fetchUserName() -> String? {
isFetchUserNameCalled = true
return "Test"
}
static func clean() {
StubInteractor.shared = nil
}
}
Thanks to this refactor, in our test assert
, we can use our new Singleton to test the value of isFetchUserNameCalled
:
XCTAssertTrue(StubInteractor.shared.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:
extension UsersRouter: UsersListNavigationDelegate {
func usersListSelected(for user: User) {
let userDetailsPresenter = UserDetailsViewPresenter(user: user, navigationDelegate: self)
userDetailsPresenter.present(in: parentViewController)
presenters["UserDetails"] = userDetailsPresenter
}
}
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.
18/09/2017 at 09:58
Having problems with this line:
let configuration = ViewModel.Configuration(interactorType: StubInteractor.self)
because
interactorType
is a let constant inside the struct so I changed it to var.(There are other ways to solve this but I just used this fix to let the compiler pass.)
18/09/2017 at 10:34
Hey Tine, thank you for pointing it out. You’re right! I’ll update asap.
10/09/2017 at 16:04
Great work 🙂
11/09/2017 at 08:45
Thank you! I’m glad you like the article!