Test Driven Development on iOS

Since I’m developing my current iOS app using test driven development, I thought I’d share some of the things I’ve learned in the process. I’ll be focusing on TDD at the unit test level and I’ll only be discussing writing unit tests for view controllers. The concepts discussed in this post can easily be extended so that unit tests cover all parts of an iOS application and not just the view controllers.

Two key ingredients to successfully writing cohesive unit tests on any platform are a test framework and a mocking framework. Granted, one can always write his or her own mocks but mocking frameworks provide a plethora of advantages in addition to simply providing on the fly mock implementations of interfaces. Since this post isn’t so much about the benefits of mocking frameworks but rather about TDD on iOS, I’ll leave it to the reader to inform him or herself about mocking frameworks. The testing framework that I’m using in my application is the SenTest kit built into Xcode. The mocking framework I’m using is OC Mock. The two key concepts I’ll cover in this post are how to write unit tests for UI methods and how to write unit tests for methods that leverage a model.

Setting up our application

In this first example we’ll set up a simple address book iPhone application. The address book simply allows the user to search for available contacts by entering the desired contact into a text box and pressing a search button. If the address book contains an entry for the entered contact, the contact will be displayed using a label. If the address book does not contain the entry, the label displays an appropriate message informing the user that the entry was not found.

The first thing we’re going to do is set up a new iPhone application and add the OCMock framework to our application. When creating the project make sure you check the check box “Include Unit Tests”. For information on how to add the OCMock framework to our application, go here and click on the iOS5 tab. Once our project is set up, the project structure should look something like this:

Project Structure
The project structure

Testing methods that interact with UI elements

First we’re going to set up our basic UI structure and test that the view controller logic interacts properly with our IBOutlets. In interface builder, drag a new View Controller element into the editor area. Add a text box, label and a button.

Address book app

Create a new view controller objective c class called AddressBookViewController. Associate the view controller in the interface builder with the AddressBookViewController class. Create IBOutlets for the UILabel and the UITextField and create an action called searchAddressBook and link it to the UIButton.

When you run the application, the address book vc should appear. Now we’re ready to write our first test. Our first test case will simply check whether or not the searchAddressBook method retrieves the text from the text field. This might seem like an incredibly simple test but it is nonetheless a valid test case since it tests one specific behavior of our use case. So go ahead and create a new test case class in the test folder. I like naming my test classes after the class they’re testing so in this case my test class is called AddressBookViewControllerTest. In the header file of the test class import the OCMock.h file. In the m file add the first test method. Naming test methods is a very important part of TDD since you’re going to be writing quite a few tests and if they’re not named appropriately, you’re going to have a hard time keeping track of what each test does. I like naming my tests according to the following convention:

testNameOfMethod_when_condition_then_expectedBehavior

So in the case of our first method I’m going to name my method:

testSearchAddressBook_when_userPressesSearchButton_then_retrieveTextFromTextField

Admittedly this method name is excruciatingly long. However, we’re never going to be calling this method in our code. Additionally, this method name clearly states what this method is testing so we’ll be able to determine the purpose of this test even months down the line when the time might come to refactor some of our code. In that situation you’ll be very grateful that you took the time to write descriptive and meaningful method names for your test methods :).

When writing unit tests I generally follow the AAA rule of Arrange, Act and Assert. That means that in the first section of the test method I set up all the objects and expectations (arrange).

// Arrange
id textFieldMock = [OCMockObject mockForClass:[UITextField class]];
[[textFieldMock expect]text];
AddressBookViewController* testee = [[AddressBookViewController alloc]init];
testee.textField = textFieldMock;

The first thing we’re doing here is setting up a mock object for the TextField IBOutlet. This mock object will act as our text field. We can now tell this mock object how it should behave for our test. In this test we’re simply telling it that we’re expecting the text property to be called. Afterwards we’re initializing our test object. This is the class that we’re actually testing. I always call my test objects testee so that I always know which object I’m actually testing. Finally we’re injecting the mock object into the view controller.

Now we can write the next part of our test, the “act” part:

// Act
[testee searchAddressBook];

Here we’re simply calling our test method. Finally we can write our “assert” part:

// Assert
[textFieldMock verify];

All we’re doing here is verifying whether or not the text property of our TextField mock was called. The complete test method looks like this:

-(void)

testSearchAddressBook_when_userPressesSearchButton_then_retrieveTextFromTextField{
// Arrange
id textFieldMock = [OCMockObject mockForClass:[UITextField class]];
[[textFieldMock expect]text];
AddressBookViewController* testee = [[AddressBookViewController alloc]init];
testee.textField = textFieldMock;
// Act
[testee searchAddressBook];
// Assert
[textFieldMock verify];
}

When we first run this test (before actually implementing our method) we should get a failing test:

Failing test

This message verifies that our method is not yet implementing our expected behavior. So in our next step we write the implementation of our method so that the test passes:

- (IBAction)searchAddressBook{
NSString* enteredText = self.textField.text;
}

Running our test we see that the test passes. Our first TDD iteration is complete :). Granted, this may be a very simple example but it still demonstrates the TDD workflow in an iOS application.

Passing test

Testing methods that interact with the model

Our searchAddressBook method does not actually meet our requirements just yet. It has to actually perform a search and display the found contact if it was found or display a “not found” message if it wasn’t found. So let’s enhance our application to do just that. The first thing we’re going to do is create a simple AddressBook model class. This class contains one method:

-(NSString*)searchAddressBookForContact:(NSString*)contact;

This method will return the name of the contact that matches the entered text if a match was found or nil if nothing was found. We’re going to leave this method unimplemented for now:

-(NSString*)searchAddressBookForContact:(NSString *)contact{
return nil;
}

Before we write our next test method, create a new property in the AddressBookViewController class of type AddressBook. Make sure to place this property in the header file so that we can inject a mock from our test class:

@property (strong, nonatomic) AddressBook* addressBook;

We’re ready to write our next test method. I’m going to call this method:

testSearchAddressBook_when_userPressesSearchButton_then_searchAddressBookModelForContact

This method will test that our model’s searchAddressBookForContact method is being called. It will also make sure that the parameter passed to the method is the text we entered into the text field. Our arrange will look as follows:

// Arrange
NSString* enteredContact = @"Paul Bradley";
id textFieldMock = [OCMockObject mockForClass:[UITextField class]];
[[[textFieldMock stub]andReturn:enteredContact]text];
id addressBookMock = [OCMockObject mockForClass:[AddressBook class]];
[[addressBookMock expect]searchAddressBookForContact:enteredContact];
AddressBookViewController* testee = [[AddressBookViewController alloc]init];
testee.textField = textFieldMock;
testee.addressBook = addressBookMock;

First we’re declaring an NSString that will hold the text that our user would be entering into the text field for this use case. In other words, this test will operate under the assumption that the user entered the name “Paul Bradley” into the text field before hitting the search button. Next we set up our TextField mock just like before. Only this time we’re not setting up an expectation but we’re stubbing out the text property. This means that we’re specifying what the text field mock should return when we call the text property. In this case we’re setting up the mock in such a way that it will return our enteredContact variable when its text property is called. Next we set up our AddressBook mock object and set up an expectation. The expectation states that the searchAddressBookForContact method is called and that the enteredContact value is passed to it. Finally we’re injecting our mock objects into the view controller that we’re testing.

Our act and assert sections look as follows:

// Act
[testee searchAddressBook];
// Assert
[addressBookMock verify];

The only thing that changed here is that we’re now verifying our address book mock object and not our outlet. When we run our test we see that it fails because the model’s searchAddressBookForContact method was not invoked. Thus we modify our implementation of our view controller’s searchAddressBook method:

- (IBAction)searchAddressBook{
NSString* enteredText = self.textField.text;
[self.addressBook searchAddressBookForContact:enteredText];
}

Rerunning our test we see that all our tests are green :). As you can see, thanks to the power of mock objects, we can test out all sorts of different user scenarios in our view controllers. I’m going to demonstrate two more test cases. These test cases will test whether the label displays the appropriate text when the user performs a search. The first of these tests makes sure that if the entered contact is found, that it is displayed using the label:

testSearchAddressBook_when_contactIsFound_then_updateLabelWithFoundContact

The arrange block looks as follows:

// Arrange
NSString* foundContact = @"Michael McDermus";
id labelMock = [OCMockObject mockForClass:[UILabel class]];
[[labelMock expect]setText:foundContact];
id addressBookMock = [OCMockObject mockForClass:[AddressBook class]];
[[[addressBookMock stub]andReturn:foundContact]searchAddressBookForContact:OCMOCK_ANY];
AddressBookViewController* testee = [[AddressBookViewController alloc]init];
testee.addressBook = addressBookMock;
testee.resultsLabel = labelMock;

Here we’re defining the contact that our model will return (Michael McDermus). We’re then setting up our label and telling it that we’re expecting its text property to be set to the contact returned by our model. Then we’re setting up the mock of our model and telling it that it should return the contact “Michael McDermus” when its searchAddressBookForContact method is called. The OCMOCK_ANY parameter tells the mock object to ignore the parameter passed. This is very useful since it keeps our test from having to know about the intricate details of the method call to our model. Since this test simply makes sure that the label displays the proper value depending on what our model returns, the parameter we’re passing into our model is not relevant here. Finally, we’re creating our testee and injecting our mocks into it.

The act and assert parts look as follows:

// Act
[testee searchAddressBook];
// Assert
[labelMock verify];

Since we’re only interested in what is happening to our label in this test, we’re only verifying the label mock object. Running our test will yield the expected failure message:

Failure message

We thus update our searchAddressBook implementation so that the test passes:

- (IBAction)searchAddressBook{
NSString* enteredText = self.textField.text;
NSString* result = [self.addressBook searchAddressBookForContact:enteredText];
self.resultsLabel.text = result;
}

Finally we write the test that makes sure an appropriate error message is displayed if no contacts were found:

testSearchAddressBook_when_noContactFound_then_displayErrorMessage

Our test looks as follows:

-(void)testSearchAddressBook_when_noContactFound_then_displayErrorMessage{
// Arrange
NSString* errorMessage = @"No matching contact found.";
id labelMock = [OCMockObject mockForClass:[UILabel class]];
[[labelMock expect]setText:errorMessage];
id addressBookMock = [OCMockObject mockForClass:[AddressBook class]];
[[[addressBookMock stub]andReturn:nil]searchAddressBookForContact:OCMOCK_ANY];
AddressBookViewController* testee = [[AddressBookViewController alloc]init];
testee.addressBook = addressBookMock;
testee.resultsLabel = labelMock;
// Act
[testee searchAddressBook];
// Assert
[labelMock verify];
}

The key differences here are that our expectation of the label mock is that its text property will be set to the error message and that the model will return nil for any and all calls made to its searchAddressBookForContact method. Running this will yield the failure message

Failure message

This makes sense since we’re simply setting the label to display what our model returned. Therefore we have to adjust our implementation so that it displays an error message when our model returns nil:

- (IBAction)searchAddressBook{
NSString* enteredText = self.textField.text;
NSString* result = [self.addressBook searchAddressBookForContact:enteredText];
if(result == nil){
self.resultsLabel.text = @"No matching contact found.";
}
else {
self.resultsLabel.text = result;
}
}

Running this yields only green tests :). We now have a lean and fairly clean implementation of our searchAddressBook method in our view controller. This is, of course, a very simple example but it nonetheless demonstrates how TDD can be applied to develop iOS applications. The next step in this application is to implement the AddressBook model so that it meets our expected requirements. This development can also be done using a TDD approach but I’ll leave that to the reader to implement :). I hope this guide provided a glimpse into TDD on the iOS platform and that it suffices to get some of you started on TDD on iOS.

About the author

christian.braunschweiler

Add comment

Recent Posts