This post will explain how (and why) to get started unit testing in iOS.

Table of Contents:

  1. Why I added unit test
  2. Add Unit test to your project
  3. Getting started with unit test
  4. Unit test example


Does this sound familiar to you?

You adjusted a function and want to check if the function produced the correct output, you build and run the app, open the simulator, input username and password, tap login, and then tap another 3 buttons to reach the screen that contains the label text generated by the function. You then found the output is incorrect, and then you adjust the function and....

I have been there and spent countless time tapping into the correct screen to check output over and over again. I remember hearing some iOS developers mention about unit test in Reddit and local meetup but I didn't get why I should write unit test at that time, until recently an user emailed me about a bug in my app and I decided to take a plunge into unit testing.

The app in question is Rapidly, it is an app to check route and fare information for public trains in Kuala Lumpur.

A user emailed me to notify that the "take the train moving towards X end station" text would display wrong result when a certain combination of start and end stations are chosen. In Kuala Lumpur train system, different lines of train can share the same rail in some station but they will end up in different terminal station, it can be confusing for new visitor as the trains arriving the same platform can lead to different destination stations.

This is the text in question:
Towards label

After receiving the email, I proceed to check the related function and make adjustment to it. To check the text output, I have to repeat this process:

Repetitive shiz

Each time I made change to the function, I have to repeatedly select two stations, tap 'Show Route' and scroll down to check if the text shown is correct or not. I found this process quite tedious and time consuming as I have to spent 15 seconds+ (including build time) just to check a line of text.

Eventually I got fed up and decided I should add unit test to my app project. I will briefly show how to add unit test to a new / existing project below.

1 - How to add unit test target to project

New project

During creation of new project, you can include unit test by checking the box Include Unit Tests.

Include Unit Tests

Existing project

For existing project, select File > New > Target. Then select 'iOS Unit Testing Bundle' and click 'Next'.
Add Target

Unit Testing Bundle

Test Target Info

You will see a window like above, Xcode will autofill the Product Name field with {Your app name}Tests, usually I left this field as is and click 'Finish', you can change the product name if you want.

Test boilerplate
After creating the test target, you can view the test boilerplate which Xcode has created for you by selecting {AppName}Tests folder > {AppName}Tests.swift in navigation bar.

Boilerplate test code

We will explain how to start writing unit test in the next section.

2 - Getting started with Unit Test

Let's kickstart with a simple test, we will add a check like this inside func testExample() :

func testExample() {
    // This is an example of a functional test case.
    // Use XCTAssert and related functions to verify your tests produce the correct results.
    let a = 3
    let b = 2
    XCTAssert(a > b, "a should be larger than b")
}

Press command + U to run the test.

After instructing Xcode to run the test, you will see Xcode proceed to build the app and run the test immediately. You should see the message 'Test Succeeded' after the test has finished running.

Congratulations! You've just ran the first test!🎉

What Xcode does when you press command + U is that it will go over all functions which have name starting with 'test' (eg: testExample, testPerformanceExample etc) and run the code inside the function.

XCT in XCTAssert is a shortform for X code Test, it is a utility function to check if an output of a function / syntax equals to true.

XCTAssert(a > b, "a should be larger than b") will check if a > b return true. If a > b return false, the test will fail and Xcode will show the error message a should be larger than b like this:

Failed test

You can try out different Assert function as suggested in autocomplete:

XCTAssert

XCTAssertNil will check if the expression return nil and show an error message if it is not nil, XCTAssertTrue will check if the expression return true and show an error message if it is not true, etc.

One thing to keep in mind is that if a function name doesn't start with 'test', it will not get executed in test!

Not executed

Pressing Command + U will run all the tests and this will take quite some time, if you just want to run a specific test function (to save time during development), you can click the diamond shaped sign (whether its a green tick, red cross or empty) beside the function name :
Specific test

In the next section, I will show how I added test for the 'Towards X' text function for my app Rapidly.

3 - Unit Test example

In this section, I will show how I added test for the 'Towards X' text function for my app Rapidly.

The function that generates the 'Towards X' string is inside a class named 'RapidHelper.h' (Yes, the app is unapologetically using Objective-C, fite me 😂)

Towards

Since this function is located inside the class 'RapidHelper' , the convention I follow is to create a unit test case file named 'RapidHelperTests' ( {ClassName}Tests ), like this:

Right click on the 'RapidlyTests' group, select 'New File' > 'Unit Test Case Class'.

new file

unit test case classes

RapidHelperTests

In the RapidHelperTests.m file, I start by importing classes that will be used by the test cases. (You won't need to do this if you are using Swift)

Import Stuff

These are the stations in question (wrong output when this particular start station and end station is used):

Stations grrr


Since the start station and end station are named Plaza Rakyat and Chan Sow Lin, I named the test function as testTowardsStringFromPlazaRakyatToChanSowLin. I can use the formula testTowardsStringFrom{StartStation}To{EndStation} for naming should I need to test other station combinations in the future.

To ensure the output of the function towardsStringFromNode:toNode is correct for the station Plaza Rakyat and Chan Sow Lin, here's the step I wrote for the test function:

  1. Initialize station Plaza Rakyat and Chan Sow Lin
  2. Call the function towardsStringFromNode:toNode by passing the above stations to its parameters, then store the result into a variable
  3. Compare the variable with the expected output
- (void)testTowardsStringFromPlazaRakyatToChanSowLin {
  /**
   Initialize Plaza Rakyat station (of AmpangSriPetaling Line)
   */
  StationNode *SP8 = [StationNode nodeWithIdentifier:@"SP8"];
  SP8.additionalData = [NSMutableDictionary dictionaryWithDictionary:@{@"name": @"Plaza Rakyat", @"line" : [NSNumber numberWithInteger:LineAmpangSriPetaling]}];
  
  /**
   Initialize Chan Sow Lin station (of AmpangSriPetaling Line)
   */
  StationNode *AG1SP11 = [StationNode nodeWithIdentifier:@"AG1"];
  AG1SP11.additionalData = [NSMutableDictionary dictionaryWithDictionary:@{@"name": @"Chan Sow Lin", @"line" : [NSNumber numberWithInteger:LineAmpangSriPetaling]}];
  
  // Run the test and check if the string produced by the function is equal to the desired output

  NSString *towardsString = [RapidHelper towardsStringFromNode:SP8 toNode:AG1SP11];

  XCTAssertTrue([towardsString isEqualToString:@"Ampang / Putra Heights"], @"Towards String from Plaza Rakyat station to Chan Sow Lin station should be 'Ampang / Putra Heights'");
}

Now that I have the test set up, every time I made changes to towardsStringFromNode:toNode function, I just need to run this individual test case to check if the output is correct instead of having to wait the simulator to load and then spent 10 seconds tapping around 😅.

Afterthought

Although this post doesn't go deep into unit test, I hope it does answer the question "why should I write unit test?" for you. It saved me a lot of time and effort on developing apps, and I hope you can benefit from it too. Next time if you find yourself tapping a lot of buttons or navigating multiple screens to check if the output is correct, it might be a good time to write unit test for that particular function.

If you are still not convinced by the benefits of unit testing, here is the full list of benefits of unit testing written by Josh Brown.