IT Knowledge from developers for developers

I believe we are well past the point of discussing whether an app should have tests or not, so I won’t bother explaining why an app should have tests and why they are useful. In this blog post we’ll start from an empty project and show how test driven development may look like on the iOS platform.

What’s TDD all about?

By utilizing TDD, you are first writing tests (that initially fail), and after that you develop code which makes the tests pass. Once you have the working code, you may wish to refactor it in order to comply with coding standards. This ensures that your app is testable, tested, and it generally improves app quality. You let tests drive your app design.

What are we developing?

The app we’re going to develop is an AddressBook which syncs contacts from a server. The complete app with the mock Sinatra server can be found here.

Let’s see the first example. Create an empty Xcode Single View Application project named AddressBook.

Xcode immediately creates two groups for us:

AddressBook – where our production code is

AddressBookTests – where our test code lives

All test classes we create belong to the Test target and all production code belongs only to the main target. That way no test code is shipped with the app.

What is going to be our first test?

When the app starts, the contacts view is shown and the first thing the view controller should do is to fetch all contacts from the server. So we can test just that.

This code is not even compilable because ContactsViewController doesn’t exist yet. Let’s create it and add it to the app target.

@interface ContactsViewController : UIViewController
@end

@interface ContactsViewController : UIViewController @end

Now, our code can be compiled, but it doesn’t test anything yet. Let’s add our testAllContactsAreRetrievedUponViewAppearing method. This method will test that contacts from the server have been requested as soon as ContactsViewController has appeared. The way we can test this, is that we assert that there are no ongoing requests before this controller appears. After appearing we assert that there is one ongoing request of type DPAllContacts (DP stands for DataProvider). The test we’re going to write is an integration test since it tests two app layers: ContactsViewController (UI layer) and DataProvider.

We also need to add the retrieveAllContacts method to our DataProvider façade. That method should add a DPAllContacts instance to the ongoingRequests array. Now the test passes:

The test passes, and it’s time for the 3rd step: refactoring. There are several issues here:

ongingRequests mutable array is exposed in a public interface

DataProvider is a singleton, and singletons are cumbersome for testing because they carry state for the lifetime of application

The test we’ve written is an integration test. Usually, it’s too soon to have integration test so early in a development. Instead, we should have a unit test where DataProvider should be a mock object.

Problem 1: Hiding implementation details

DataProvider interface exposes ongoingRequests mutable array which is an implemention detail. Besides, it allows someone else to modify it, which should be sole responsability of DataProvider.

Solving problem no.1 is easy: just move ongingRequests mutable array into .m file and expose the public method -(NSArray*)getOngoingRequests in interface file. The implementation can be trivial:

We should also adopt our testAllContactsAreRetrievedUponViewAppeared method to reflect these changes.

Problem 2: Dealing with singletons

Singletons are quite common in iOS: UIApplication, NSFileManager, NSUserDefaults, CSSearchableIndex, NSNotificationCenter, UIAccelerimeter… Even though they seem convenient to use, they are quite cumbersome when writing tests. The reason for this is that singletons maintain state across the whole app life, or while executing tests. That means that one test could affect the next one, which is a big no-no in Unit testing. Some common singletons in everyday iOS app are:

Context – where common properties are kept such as a current user and/or other global properties

TransferManager – layer which handles file uploads and downloads

DataProvider – layer which communicates with REST API

History – support for undo/redo operations

CoreDataStack – database layer

We can deal with singletons in several ways. One is to pass everything as a parameter to a, say, UIViewController and that one could pass references to other objects as needed. One other solution is to, instead of having multiple singletons, have only one real singleton which would have references to all other potential singletons. I usually name that singleton ServiceRegistry.

If we now define ServiceRegistry as

#define SREG ((ServiceRegistry*)[ServiceRegistry instance])

#define SREG ((ServiceRegistry*)[ServiceRegistry instance])

in PrefixHeader.pch file, then just by typing SREG. we get a list of all services in our app:

We can do the same with our DataProvider. By creating ServiceRegistry as singleton which will hold a reference to it, DataProvider is no longer a singleton and thus it’s much easier to mock. Which leads us to a solution of problem no.3.

Xcode vs. AppCode?

If you use AppCode for iOS development, there is one additional step that needs to be done. In AppCode, goto Run → Edit Configurations, click on the + button and select XCTest/Kiwi. It is useful to create two test configurations:

one for all tests

one for the specific test case

With the second one we can test only one test case without executing all tests after a single line change. To test a single class, enter the class name in the Class text field as shown in the image below.

Conclusion

We saw that by writing tests, we let them drive our app design. We had to create a DataProvider layer which would serve as a façade to our backend server. We also created ContactsViewController and made sure that it retrieves all contacts after it appears. The whole project (with somewhat modified code) is available on bitbucket. This was a basic and introductory article on how to write tests and how TDD may look like in iOS development. There are many articles and books about TDD. Feel free to start with the Wikipedia article, and if you’re interested in a book, I definitely recommend ‘Test-Driven iOS Development’ by Graham Lee.