Make the code testable in Swift

Soheil Novinfard
6 min readMar 29, 2020

--

Kudos to undraw and lukaszadam

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.

[ Image is taken from 360logica.com ]

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.

Here, 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.

The Standing data object is defined as follows:

Standings Datasource

And the request class is defined as follows:

Standings Request

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 dataController.

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 requestData method.

Figure 1 — Request is not accessible via the class

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 [1].

There are three common types of dependency injection:

Method Injection

We can pass the request to our method by one of its parameters.

Figure 2 — Method Injection

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.

Property Injection

We can pass the request as one of the class properties.

Figure 3 — Parameter Injection

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.

Constructor Injection

We can pass the request to the initializer of class:

Figure 4 — Constructor Injection

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 [2].

Endpoint Request

For defining our endpoint request, let’s review the StandingsRequest implementation.

Figure 5 — URL in Endpoint Request

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.

Standings Network Request

Entity Request

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:

Figure 6 — Standing Entity request footprint in StandingsDatasource

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:

Entity Request

For making a standing entity’s request, we need a predicate on Standing entity by using the groupName and tournamentId. Let’s define it as follows:

Standings Entity Request

Now it’s time to come back to our datasource and refactor it with new StandingsEndpointRequest and StandingsEntityRequest :

Standings Datasource after Refactoring

Unit Testing

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 tournamentStandingsSample.json :

Sample JSON response for Standings

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 Group B.

We write three unit test for checking the parsing, duplicate parsing, and group filtering:

Standing Unit Tests

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 number10 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.

Conclusion

New 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 StandingsRequest.

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.

References

--

--

Soheil Novinfard

Accomplished software engineer with +11 years of experience in collaborating with the product and design teams, helping the travellers at @Skyscanner