Mock Dependencies: Instance and Configuration Injection With Swift

Original article: https://marcosantadev.com/mock-dependencies-instance-configuration-injection-swift/

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:

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 since ViewModel 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 Configuration 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 InteractorTypeprotocol:

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.

Configuration 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 Configuration Injection is using a struct Configuration where we store 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 configuration 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 {
let 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 Configuration 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 Configuration 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 Configuration Injection in the StorageKit class CoreDataStorage.

Conclusion

I usually try using Instance Injection as much as possible to avoid using static variables in my unit tests. I created Configuration 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.