Unit Testing Swift Code with Protocols
--
In this post we’ll look how Protocols and Dependency Injection can help us in writing pure testable code in Swift.
Purity of the class or API is determined by the dependency it has. For example, if you have an API to find sum of two numbers that accepts those two numbers as parameter, it is pure. There is no dependency to that API.
But if you have an API that adds a number to a global variable and accepts only one argument, it is not pure. Its response depends on the value of global variable and the arguments. Assume this API is under a class Accumulator and the global variable which we defined earlier was a property of this class — then the class is pure, but the API is still not.
Making API pure helps us in writing unit tests much easier. If we can’t make an API pure, we should try to re-write the class involved to be pure.
In this post we can try to make an impure class and function pure, thereby testable 🤘🏻
User Class Example
Testing the User Class
We are going to test the User class which we wrote just now. Only purpose of the class for now is to fetch the profile image from some service, apart from saving the display name.
But if you notice the fetchProfileImage API, it is tightly coupled with the ImageDownloader class within the API, which makes it impure. Once we call this public API, we don’t have much control over the network calls that ImageDownloader makes in order to fetch the image.
Just to fix this, let’s rewrite the fetchProfileImage API a bit.
The main change here is to inject the downloader as an API parameter. Wonder how this will help us in testing the API?
Here it is. Since you are supposed to pass the downloader as a parameter, the API will even take a custom subclass of ImageDownloader where you can override the fetch API not to use any network connections, but respond with the image or error you want to return as per the testing scenario.
Let’s see an example:
Here we have a MockImageDownloader that takes response as init parameter and respond that for fetch request. Now the downloader is under control for during testing, so you don’t have to worry about the network calls.
This is called Dependency Injection!
Using Protocols
Let’s see how we can again improve this code. Currently our fetchProfileImage API accepts a concrete type ImageDownloader. Let’s change it to accept a protocol ImageFetcher.
You can read these if you are not familiar with Protocols:
Let’s do this:
This is a very minor change, where we replaced ImageDownloader class with an ImageFetcher interface. The beauty of ImageFetcher is that it can fetch image from any source depending upon the implementation of concrete object we pass.
Final User Class Implementation
Testing
Here is how we can test the above code:
Since we have a file fetcher implementation already in our main bundle, you don’t even need to have a separate Mock implementation, given you have resource available in the documents directory in this case (You can even have it in your application test bundle).
Using protocols and dependency injection, you can write testable code. Object injection can be via an API as in this example or via a class to make an API or class pure without having any unknown dependency inside.
Read this if you want to learn more ways to inject dependency in your code.
Let me know how you have used these techniques to shoot the coverage in your projects 😉