When I previously wrote about better testing of view controllers on iOS I alluded briefly to the strategy of breaking the ViewController into a ViewController and a Presenter.
Again, I won’t go into mocking here, but you need a mocking framework and some understanding of what mocking is for this to make sense. Currently, I’m using OCMock. Also, XCTest is not the best documented, but here is a handy list of asserts.
This strategy means that for each ViewController there are two classes, MyViewController and MyViewPresenter. This inherits from top level classes which I have imaginatively named ViewController (inheriting from UIViewController) and Presenter (inheriting from NSObject).
ViewController and Presenter
The aim of the Presenter class is to expose the things that any ViewController might want to access, making it unnecessary for MyViewController to know about the MyViewPresenter class.
This class handles setting the presenter, ensuring the navigation buttons are set up properly, and that viewLoaded gets called.
Testing ViewController and Presenter
Neither of these classes do very much, but they provide us with a way to create a seam which is how we write unit tests. It might seem unnecessary to write tests for these, but that just means that the tests will be quick and simple. I err on the side of if it exists, test it. Both because it’s normally faster to just test it than decide every time, and also because I am often not as smart as I’d like to think I am, therefore am liable to break things.
I’ve opted to use Strict mocks rather than their more forgiving brethren, because I want to know exactly what is going on. This makes the tests a little more brittle than strictly necessary, but I find it a helpful learning mechanism.
Example: HomeViewController and HomeViewPresenter
This is the home screen for an image app, with a simple UI featuring 3 buttons – take a picture, show the gallery, and “inspire” which is not yet implemented.
The init method is exposed for testing, but the ViewController is instantiated in the app by calling createViewController.
Notice, the view elements are accessed through the views and the actions added to them all call methods in the Presenter itself. The Presenter is also the delegate for the ImagePickerViewController.
The ViewController exposes the view within it, and a method for launching an ImagePickerViewController.
You can see as a result the ViewController has very little code, because all it is doing is presentation.
The tests for the HomeViewController are very simple.
The presenter is a little more interesting. Notice how we capture the action added to the UIButton and call it using sendActionsForControlEvents:.
When starting from scratch, this method makes it so easy to write unit tests and doesn’t really increase the amount of code required per ViewController, just splits it in two. It’s harder to retrofit to an existing codebase, but it is possible.
Start with the top level classes, and then choose the simplest ViewControllers in your codebase to split. Add tests for them. Then choose progressively more complicated ones. You may need to add more methods to the top level ViewController and Presenter, depending on the complexity of your app. Often the reason why we don’t add tests for ViewControllers is that we never have, so starting is the hardest part.
Finally, on UIAutomation tests, I don’t see this as a replacement for KIF or other UIAutomation tests. These are great for making sure that every screen on the app loads OK, for example, and I still see apps sometimes (especially as apps have got larger) where some unloved corner of the app means that that what should launch a new screen just crashes. However these kind of tests allow us to get into details with less setup than is required by UIAutomation tests, making them easier and less time-consuming to debug.
For more detail on this and other aspects of iOS unit-testing you might find my digital workshop helpful.