Dependency Injection and Testing with protocols in Swift

Dependency Injection has been around for quite some time now, in fact most of us are familiar with pre-software dependency injection because at some point in our lives we studies some algebra, or some similar mathematical science where we used variables like x and y to represent changeable values in an equation. In a nutshell this is really all that dependency injection boils down to; simply put dependency injection is the process to assigning values to parameters within an object. In Swift this can be done in a class or a struct using variables or constants through direct assignment, or initializers.

This mostly works great for primary code, but there are some limitations when we start to think about testing or as a code base starts to grow. One possible issue is that it could start to get a bit messy.

Say for example if we have two objects that really are very similar, maybe a couple of networking services, each targeting a unique endpoint, but largely doing the same thing at a high level like a set or RESTful calls. If two different developers worked on these two services independently, its possible they end up making slightly different choices when it comes to naming things. This creates a code standard that is not uniform, and over time can become very hard to navigate and maintain.

The second issue that arrises is that using this type of direct dependency management we are not given any flexibility in the implementation of functions, any functions defined by the object are set. We can use inheritance to potentially override them, but this has the potential to start to become problematic especially in at testing scenario, creating complex on-off overrides again can lead to confusion and difficult to maintain code.

Don’t get me wrong, there are times with using override is the right answer, but that is beyond our scope today

This is where the Swift Protocols start to really stretch their wings. In Swift a protocol is essentially a definition. Or more specifically, a protocol is a way for us to define required or optional properties and behaviors for any object that subscribes to it.

Without looking too hard, it should be pretty easy to see how protocols and dependency injection are related. But, let’s take a look none the less. By subscribing to a protocol, an object essentially agrees to implement whatever the requirements of that protocol are. One thing to note is that a protocol cannot contain private properties, however, we can still manage accessibility using the getter and setter declarations within the protocol, this allows us quite a bit of flexibility in the implementation.

It’s important to note privately set property values can still be injected via initializers, but once they are set they are safe against external mutation

Protocols and testing…

So this is great, we can inject our dependencies, and have a nice neat primary code base, but where things really get interesting is when we leverage this to support testing. The beauty of a protocol is that it only defines the requirements, but it does not say anything about how those requirements are satisfied. So one subscribing object may implement a function in one way, and a different object may implement the same function in a different way. In a similar vein two subscribing objects may satisfy a variable assignment with separate values that subscribe to their own related protocols or types. This is fantastic because it means that we are able to create mocks for testing and directly control the outcomes of any implementations related to the associated protocol.

So let’s check out a common pattern that we might find in an app. In this example we have a view controller and it has a view model which provides data to the view.

Start with a protocol for the view model and the associated implementation…

This protocol defines a set of properties as well as a function, requestArticles(). When this function is called it makes a GET request which returns a set of Articles. These are assigned to the local articles property of the view model and the completion is called.

Now let’s take a look at our view controller…

Keep in mind that this class is not fully fleshed out, but works great for our purposes in this post

When viewDidLoad() is called it makes a call to the view model and delegates the associated work through the requestArticles() method. When this method completes we update our view. We can also see that in viewDidLoad() we inject the value of the filterArticles property as a closure. Now let’s take a look at related testing.

Testing concerns…

If we just go directly at this in a test we pretty quickly run into an issue. We can of course inject an instance of NewsFeedViewModelImplementation() into NewsFeedViewController for our tests, but when we try to test the viewDidLoad() method we will run into a problem because we don’t have access to HTTP services from our testing environment, nor should we!

This is where protocols save the day! In NewsFeedViewController notice that our viewModel property is constrained to a protocol, NewsFeedViewModel, not the implementation of it. What this means is that we can provide any object to NewsFeedViewController for this property as long as it subscribes to the aforementioned protocol. This is great news because it means we can easily inject a mock into our testing instance of NewsFeedViewController and not only avoid the pitfalls of HTTP in testing, but also have total control over what actually happens when the test calls through to requestArticles() or any other function for that matter.

First let’s create a mock to inject into our test instance of NewsFeedViewController…

You can create mocks like this by hand, but I like to use Swift Mock Generator, check below for a link
Similarly we can create mocks based on class objects if needed, but often these have to be done by hand

Now inject the mock(s) in our test…

Note that a mockTableView is injected as well, allowing for deeper testing of the completion block

We have a very clear testing strategy here, not only are we able to work around the issues with HTTP in the testing environment, we also are able to write clear tests that are reliable. You can also see that we are able to mock the tableView object, this is because, similar to the viewModel, its declaration is bound to a type not an implementation. This is a great way to avoid the time suck of UITesting in Xcode, but still ensure comprehensive test coverage.

Now let’s look at closure injection and how that plays into testing…

If you take another look at the protocol you might notice the variable filterArticles, this property is defined as a closure which takes a boolean as an argument. Check out the code in viewDidLoad() where this closure is assigned.

You see here that within this closure we filter the values that are returned from viewModel.articles and update the featuredArticles value on the view controller.

The view controller calls this from the didTapFeatured() method. At first glance this may not seem like there would be any issues with testing, however on closer inspection there are two issues that might arise. The first is that the setter for articles in NewsFeedViewModelImplementation is private. The second issue is that if we are specifically trying to limit our testing scope to test the tap action on NewsFeedViewController we might have trouble clearly extracting the purpose of the test because we have to reference the collection of articles as a part of the expectation. We should view tests as not only an affirmation that our code works correctly, but also as a source of documentation for our code, and this type of ambiguity is troublesome to that cause. Take a look at the following two code examples to see this more clearly.

First using articles as the confirmation to the expectation…

Immediately we have some problems! First, we are not able to inject the articles collection into the viewModelImplementation because the setter for this value is private, this means that no matter what we will always have an empty array when we try to access the articles parameter. The second issue is that we are forced to build our expectation around the articles value on the viewModelImplementation object, but that doesn’t match the declaration of the test. Our declaration is “it should invoke filterArticles on the viewModel”, it doesn’t say anything about articles matching. In other words, the name of the specific test should have a sensible relationship to the expectation of the test.

We could go back and change the setter on our NewFeedViewModelImplementation, but that would be very bad form! Changing our production code to meet the needs of a test is a recipe for disaster! There is probably a good reason that we made that setter private in the first place, we shouldn’t be compromising that. Fear not! Yet again protocols come to the rescue and allow us to leverage dependency injection to support valid tests.

This time let’s inject a testable value…

Now we have a controlled, and robust test suit that insures that things are working the way we expect them too. Notice also that since we are able to inject a stub value for articles into our mockViewModel we have no problem testing that the filter method we set in out view controller works the way we expect! This is awesome!

Wrapping up…

Testing should be a straight forward, clean part of our code base. It should be teated with as much care as we enlist when developing our primary concerns, and as such should be seen as an extension of the documentation that we maintain. By using the simple principles involved in protocols and dependency injection we can ensure that we develop a robust testing strategy that will reduce mistakes, errors, and bugs! So the next time you set out to write a test, ask yourself first to give it the love it deserves and then, consider trying out a protocol or two to help you along the way.

It should be noted that I use a great tool called Swift Mock Generator to generate my testing mocks. This tool only requires a protocol, and it happily will do the rest!

I really like to write my tests using the Quick and Nimble testing framework. I find this framework to be a great improvement over XCTest because of how readable and sensical the testing syntax is.

Check out the repo for this post here

Until next time…

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s