How to write better unit tests in Swift
Do you know these moments that you start an app convincing yourself you need to certify quality by writing as much unit tests as you can? Until that moment you find out it’s almost impossible or takes so much time to write the proper unit tests. Well I had these moments and started realising that a good architecture of your app helps writing better unit tests.
Good architecture comes first
In order to write better unit tests you need a consistent architecture. The MVC pattern is definitely not the best architecture if you want to write better unit tests. Most of the time you’ll end up with a very large View Controller that has all kinds of jobs in it: a Massive View Controller.
In a good architecture each component acts as a black box independent of any other component. What’s going on inside the black box you don’t know and you don’t care. But these black boxes should have well defined inputs and outputs. In a consistent structure it should be easier to identify components, test subjects, inputs and outputs.
Code independence is achieved by the use of protocols in Swift. With the use of protocols a component doesn’t own another component directly, but has an indirect reference to that component via a protocol. In this way both components don’t know about each other, but are both depending on the same protocol.
When it comes to sending data from one component to the other, model-structs can provide data independence in Swift. By creating separate models for data requests, data responses and view models, you avoid that when one model changes you also have to change components other than the objects that use the models as input or output. This applies to their tests as well, which makes those tests less fragile.
A nice pattern I ran into was the Clean-Swift architecture which uses the VIP-pattern. In this pattern everything goes in one direction through the same cycle. It starts from the View Controller, to the Interactor, to the Presenter and ends back at the View Controller. Two components communicate with each other via a protocol which contains both the output and input logic.
The View Controller handles display logic, the Interactor business logic and the Presenter presentation logic. For unit testing it means you can test all protocol methods at each boundary so it will fully cover all components.
What goes where?
In the beginning it can be unclear what to put where in the code. To better understand the implementation of the pattern that we strive for, I will give you an example. In this example I show a list of movie titles and their release year in a tableview controller.
The model of a movie looks like this:
First I create a ListMoviesViewController, ListMoviesInteractor and ListMoviesPresenter with BusinessLogic-, PresentationLogic- and DisplayLogic — protocols.
If you start writing app code it’s easiest to start with the View Controller, Interactor and then the Presenter. When you start writing unit tests it’s easiest to start with the Interactor, Presenter and then the View Controller. In the VIP-pattern the View Controller not only communicates with the Interactor (and Presenter), but also with the view.
I start with the View Controller in which I want to setup the VIP-cycle and fetch movies to display. The View Controller holds a reference to an Interactor that conforms to the Business Logic protocol. The View Controller will ask the Interactor to fetch movies via a worker/service.
I will use dependency injection and instantiate the View Controller with an Interactor that conforms to the Business Logic protocol. In both the designated initializers init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) and required init?(coder aDecoder: NSCoder) I call a setup method that initializes and sets a Presenter and an Interactor and connects the Presenter back to the View Controller:
In viewDidLoad() I call the fetchMovies() method which will ask the Interactor to fetch the movies:
So at the same time I create in the ListMoviesInteractor file the BusinessLogic protocol and make the ListMoviesInteractor conform to that protocol:
This class has a reference to a Presenter that conforms to the Presentation Logic protocol and a reference to a worker/service that conforms to the MoviesWorker protocol that will fetch our movie-models. They’re set in the initializer as well.
In the Interactor class I implement the fetchMovies method which asks the worker for movies and provides the worker a completionHandler to ask the Presenter to present the movies:
The Presenter is conforming to the Presentation Logic protocol and has set up a weak reference to the View Controller to prevent a reference cycle. Next to that it contains the logic to convert the movie-models into viewmodels. The viewmodels will display the release year of the movie as a string, so it looks like this:
I create an extension in the viewModel with an extension to create the viewModel right out of the Movie model and put some formatting logic:
The Presenter will pass these viewmodels back to the View Controller so the View Controller can use them to populate the textfields in the view:
Here you can see that the View Controller needs to conform to the DisplayLogic protocol:
The View Controller, which is a TableView Controller in this example, has a property private var displayedMovies: [ListMoviesViewModel] = . This property is set to an array of viewmodels that will populate the tableview cells via the protocol method:
In the func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell from the TableView Datasource protocol, the cell is configured with the viewmodel:
Finally the TableView cell implements a method to configure it’s views with the view model:
I’m really curious what you’re experiences with this architecture are and if you experienced pitfalls or downsides of this pattern.
In the next part of this article I will write about how to write the unit tests. Learn about the structure of a unit test, how to set up unit tests and what stubs, fakes and spies are.