Make the code testable in Swift
Having a reliable and durable iOS app or framework depends on many things, but no one can deny the necessity of unit testing and UI testing for this purpose. The question comes up here is that are all codes tastable? No, unfortunately!
In this article, we review an example of improper code for testing and try to suggest some ways to solve the issue by applying various software techniques.
We start with an example of inappropriate code for testing purposes. After reviewing the scenario, we are going to look at how we can make it testable. The code is abstracted to focus on the main subject. You need to be familiar with
CoreData beforehand to get the most of the example. But even if not, the description is sufficient to picture the problem.
We have a class named
StandingsDatasource which is responsible for requesting the data from the network and load the parsed data in dataController (dataController is a sort of fetchResultController which can give you an array of data objects). We persist in CoreData in this scenario.
getEntityName defines which data object type (CoreData entity name) will be returned and
getPredicate determines which objects of that type should be filtered and returned in the dataController.
Standing data object is defined as follows:
And the request class is defined as follows:
We want to write some unit tests by which we parsed the data correctly. As the output of the datasource, we can fetch the desired objects in our
To start writing our tests here, we need to subclass
StandingsDatasource to have our local datasource. Our local datasource is going to read its data from a JSON file rather than network response. The reason behind this is that we may have a different response (result in different parsed standing objects) as the endpoint updates time by time.
By looking at
StandingsDatasource , we found the first issue: We don’t have access to request object to be able to change it to local request, because
StandingsRequest is initiated inside the
We can use the dependency injection technique to overcome the issue. Dependency injection means one object supplies the dependencies of another object. The “injection” refers to the passing of a dependency (a service) into the object (a client) that would use it .
There are three common types of dependency injection:
We can pass the request to our method by one of its parameters.
We call this method injection, as we injected our dependency in the method declaration.
This is not the desired behavior here, because the
requestData is called internally in the base class
ManagedDataSource . So the only way to inject our request is the same, by overriding the method in our local datasource class.
We can pass the request as one of the class properties.
Here, we inject the request into the
StandingsDatasource class. So after initializing the object, we can pass the request. The benefit of this way is we don’t have to change anything related to
requestData when we subclass our local datasource. All the logic is encapsulated inside the method. However, it’s still not good enough. Because we have to define it as optional property, while we can’t imagine a correct datasource without a request dependency.
We can pass the request to the initializer of class:
The request is assigned inside the class initializer, thus, every instance of
StandingsDatasource and its subclasses would have the initiated request property.
We chose constructor for the dependency injection, but based on the case it can be different.
Now let’s look at the second issue. We have a single data type for our datasource request,
StandingsRequest , but we have two types of requests: One of them gets the data from the endpoint and the other one fetches the data from our persistent module, CoreData.
These are two different concerns and can be encapsulated. We call this design principle in software architecture as “Separation of Concerns”. It asks us to separate the computer program into distinct sections such that each section addresses a separate concern. Each concern can be a set of information that affects the software .
For defining our endpoint request, let’s review the
As we can see, the only element needed for defining the URL is
tournamentId. So any data structure has
tournamentId and conforms to
RequestProtocol is enough and no other constraint is needed, in other words, we have a protocol here. Now we can re-define our Endpoint request as follows:
We added default implementation for
parser(parseDelegate:parentContext:) method because it is common among all implementation of the protocol, although it’s not necessary to be the same.
We declare an implementation of
StandingsEndpointRequest which works with network requests.
The second concern is about requesting the entity from our persistent module. We saw the footprint of this request inside the datasource class in two places:
For requesting an entity in our persistent module (Core Data) we need to know of the entity’s name and related predicate of type
NSPredicate. Thus, we can define our
EntityRequest protocol for any entity request in the persistent module as below:
For making a standing entity’s request, we need a predicate on
Standing entity by using the
tournamentId. Let’s define it as follows:
Now it’s time to come back to our datasource and refactor it with new
We have refactored our code to testable version. In this section, we are going to write some unit tests to see how our changes eased the testing purpose.
We got a sample of the JSON response and saved that in a file named
It returns 4 Standing entities. Three of them are in the tournament with id
10 and the last is in the tournament with id
21 . Two of the three standings of the tournament 10’s are in
Group A, and the other one is in
We write three unit test for checking the parsing, duplicate parsing, and group filtering:
First, we have defined
StandingsEndpointLocalRequest which looks for local JSON files for endpoint requests.
We created a factory method named
testDatasourceFetchStandings that has a default value for reading from our stored JSON file and tournament with id number
10 without filtering groups as passed nil for it. The
MemoryCoreDataController is a class that helps us create in-memory core data storage as part of our persistent module. It is a good idea to have this setting to avoid collision between the main app and the testing app’s object storage in unit tests.
StandingsEndpointLocalRequest class can easily create a request with a local file that lets us focus on data source functionality with sample constant JSON file. If you check our initial request class, we couldn’t test it this way unless we subclass
Additionally, we didn’t subclass our datasource. Instead, we passed our local request injected in the initializer. So our implementation encapsulated and remained intact there.
-  Dependency injection https://en.wikipedia.org/wiki/Dependency_injection
-  Separation of concerns