Site icon Accidentally in Code

Better Testing of View Controllers on iOS: Part 2

bunny in a bowl
Credit: Flickr / jpockele

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

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.

Presenter Interface

Presenter Implementation

ViewController

This class handles setting the presenter, ensuring the navigation buttons are set up properly, and that viewLoaded gets called.

ViewController Interface

ViewController Implementation

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.

PresenterTest

ViewControllerTest

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.

HomeViewPresenter Interface

The init method is exposed for testing, but the ViewController is instantiated in the app by calling createViewController.

HomeViewPresenter Implementation

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.

HomeViewController Interface

The ViewController exposes the view within it, and a method for launching an ImagePickerViewController.

HomeViewController Implementation

You can see as a result the ViewController has very little code, because all it is doing is presentation.

Testing HomeViewController

The tests for the HomeViewController are very simple.

Testing HomeViewPresenter

The presenter is a little more interesting. Notice how we capture the action added to the UIButton and call it using sendActionsForControlEvents:.

The End

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.

Exit mobile version