Improving code testability with Swift protocols
As developers, one of our biggest challenges is to achieve high code testability. The tests are very useful to ensure that the code you have developed works as it should and that when adding new features, nothing has been broken. Also, when you work in a team, there are many people who modify the project. So ensuring the integrity of the code is important as well.
There are many kinds of tests, but they shouldn’t be problematic or complex. So, why do not many developers do it? 🤷🏻♂️ The main e̶x̶c̶u̶s̶e̶ reason is lack of time. I believe, one of the big problems is that our code is too coupled between layers, classes and dependencies with external frames.
I would like to prove that creating an abstraction layer of a framework or decoupling the classes should not be a difficult task.
Imagine we need to develop an application, which needs to know the user’s location and, therefore, we need to use CoreLocation.
Our ViewController could look like this:
It has a locationManager as CLLocationManager, to request the user’s location or request authorization, if appropriate. It also conforms CLLocationManagerDelegate protocol, where it receives locationManager output.
Here we can see that our ViewController is coupled with CoreLocation and other problems related to the separation of responsibilities.
Anyway, let’s create the tests for our ViewController. This could be a good example:
We can see our sut (System Under Test) and one of the possible tests. There we request the user’s location and store it in our local variable (userLocation).
Here the problems begin to appear… CLLocationManager manages the request and it is not a synchronous process, so when we check the stored location is still nil. Also, we may not have authorization to request the location, in this case, the location will also be nil.
Now, we have some possible solutions! Let’s test the ViewController without testing anything related to the location, create a subclass of CLLocationManager and simulate the methods or try to do it correctly and decouple the CLLocationManager from our class. I choose the last 🙂
Protocol Oriented Programming (POP) to the rescue
“At the heart of Swift’s design are two incredibly powerful ideas: protocol-oriented programming and first class value semantics” - Apple
POP is a powerful tool for developers and Swift is undoubtedly a protocol-oriented language. So my proposal is to solve these dependencies using protocols.
First, to abstract CLLocation, we will define a protocol with only the variables or functions that we need for our code.
Now, we can get a location without CoreLocation. So if we analyze the ViewController, we can see that we do not really need a CLLocationManager, only someone who provides us with the user’s location when we request it. Therefore, we will create a protocol that contains our needs and whoever conforms this protocol could be the provider.
In our case, we have created UserLocationProvider. This protocol specifies that we only need a method to request the user’s location and the result will be through the callbacks that we provide.
We are ready to create a UserLocationService, who conforms that protocol and who provides us the location. By this way, we have solved the dependency with CoreLocation in our class, but wait… UserLocationService needs request the location by CLLocationManager…so, it seems the problem has not been solved yet 😅
Protocols to the rescue again, simply create a new protocol to specify what is a location provider for us:
We have extended the CLLocationManager functionality, conforming our new protocol.
Now yes, we are ready to create the UserLocationService 🎉, it would look like this:
UserLocationService has his location provider, but he will not know who he is, for him it does not matter, he only needs the user’s location when he requests it, the rest is not his responsibility.
The extension to conform CLLocationManagerDelegate protocol, is needed because we are going to use CoreLocation. But how we will see in the tests, we don’t really need it to verify that our class works fine.
We could add any kind of delegate inside the protocol, but for this example it’s too much, I think 🙂
Before to start with the tests, let’s see how our ViewController looks like using UserLocationProvider instead of CLLocationManager.
Looking this code, we conclude that now, our ViewController, has less code, fewer responsibilities and is more testable.
Let’s go with the tests. Firstly, we are going to create some mock classes that we need to test our ViewController.
Using these mocks, who we can inject whatever results we need, we will simulate how an UserLocationProvider works. So we will put focus in our real target, the ViewController.
We have created two tests, one checking if we don’t have authorization to request the location, the provider doesn’t provide anything. And another one, the opposite case, if we are authorized, we should obtain the user’s location. And as you can see, the tests are passed!! ✅ 💪
Besides the ViewController, we have created an additional class, UserLocationService, so we should cover him as well.
LocationProvider needs to be mocked, since it’s not the target of this test.
A lot of tests could be created, but verify if the provider says that we have authorization, if not request it and otherwise we request the location, could be one of them.
As you can imagine, there are many ways to decouple your code and this publication is only one of them. But I think it could be a good example to show that testing is not a difficult task.
If you remember the image in the top, you can see LEGO bricks, it’s why I think they explain very well what is decouple and abstract your components. At the end it's defined to a specific way to connect them, but, the color does not matter.
Perhaps one of most lazy tasks is creating the mocks, but for that already there are libraries and tools and facilitate this work, like Sourcery. Also, here is an article by my workmate, Hugo Peral, explaining how use Sourcery to save time doing tests. Or this one by John Sundell, it gives more details about how make mocks.
Nothing else, thanks for reading this publication. Share it if it was useful for you or if you think could be useful for someone 😉. Feel free to comment below if you have any doubts or any suggestion of improvement.