Why is UI testing so hard? Because the framework usually creates the UI objects for us, and we only fill in the blanks. It is expensive or impossible to construct all UI elements needed in a test. The Humble Object pattern is a way to test the logic of UI objects.
How can we make code testable when it is too closely coupled to its environment? We extract the logic into a separate easy-to-test component that is decoupled from its environment.
Massive View Controller
The problem begins with a massive view controller. Historically, OS X separated mediating and coordinating controllers.
The mediating controllers together with the Cocoa Bindings technology were responsible for the data flow between view and model objects. The coordinating controllers were used to get several objects together for a specific task. They could respond to delegation and action messages, manage object life cycles, and establish a connection between objects.
The last sentence basically screams the violation of the single responsibility principle. The massive view controller problem appeared in OS X and got only worse in iOS.
iOS doesn’t support Cocoa bindings. The roles of mediating and coordinating controllers merged together leading to the Extremely Massive View Controller problem.
A view controller has a strong coupling to its view. Therefore, after all the irrelevant roles have been extracted from it, a view controller can be perceived as just a view. To some degree, a view controller is basically a view decorator providing the lazy-loading mechanism.
The outside world can just tell it to show some data and doesn’t have to know about the UIKit-specific details of presenting it. All labels, text fields, and image views are encapsulated inside a view controller, and only strings and images are exposed.
What’s left in the view controller is a simple mapping of values it receives from the outside to the UI elements of the view it controls. The view controller becomes very humble.
The Clean Architecture
To make a view controller humble, all irrelevant roles have to be extracted away from it. For the purpose of testing the UI, it doesn’t matter how exactly it is done. However, one can turn to the existing, well-known techniques for separating concerns.
The one that stands out is called The Clean Architecture with its derivative in the iOS world called VIPER. This approach summarizes what other well-known architectures have in common, where the business logic, the presentation logic, and the delivery mechanism should go.
The example below uses the Clean Architecture terms for naming objects.
The example shows how a GPS tracking app could present the track summary.
The data for the presentation is originally prepared by an interactor object implementing the specific use case, the one of showing track summary.
How exactly the TrackSummaryInteractor gets the result data is unimportant for the UI testing example. It could work with some track repository to find the needed track and get only the required data. When the work is done, it notifies its output with the simple result data structure.
All presentation logic—converting timestamps and distances to strings—is implemented by a presenter object.
Being a use case’s output, the TrackSummaryPresenter converts the received data to a form that is ready to be used in UIView, but without any UIKit-specific details. Like the use case’s output, the presenter’s output is expressed in a protocol, so it becomes trivial to test the presentation logic.
All that’s left for the view controller is set the prepared values to the relevant UI elements.
This, very last, part is not covered by tests. But it is left so humble, so trivial, that it can be verified just by looking at it. There’s still a chance that a mistake will be made here, but it’s brought to a minimum.
In the end, all the parts are composed together:
The UI objects often end up having a lot of logic. This logic is hard to test because we don’t have control over the API and the lifecycle of the UI elements. The Humble Object pattern can be applied to extract the logic from the UI and test it independently. While doing so, the Clean Architecture can be taken as an example of a strict role separation.