Test your app function without opening a simulator and clicking 6 buttons

Read Axel Kee's guide on how to quickly test the functionality of an iOS app without needing to open the app: he'll explain how (and why) to get unit testing started in iOS.

A guide on how to test the functionality of an iOS app quickly, without needing to open the app. This post will explain how (and why) to get unit testing started in iOS.

Guest post by Axel Kee, software developer. The original post appeared on Fluffy.es

Axel is a software developer based in Kuala Lumpur, Malaysia. He has worked mostly on iOS using Objective-C and Swift for around 4 years.

Table of Contents:

  1. Why I added a unit test
  2. Add a unit test to your project
  3. Getting started with a unit test
  4. A 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 unit tests in Reddit and at local meetups but I didn't get why I should write unit tests then, until recently a 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, 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 a wrong result when a certain combination of starting and end stations are chosen. In the Kuala Lumpur train system, different lines of trains can share the same rail in some stations but they will end up in different terminal stations, so it can be confusing for new visitors as the trains arriving the same platform can lead to different destination stations.

This is the text in question:



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

Each time I make a 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 spend 15 seconds+ (including build time) just to check a line of text.

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

1 - How to add a unit test target to project

New project

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

Existing project

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

You will see a window like the one above: Xcode will autofill the Product Name field with {Your app name}Tests. Usually, I left this field as is and clicked Finish, but 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 the navigation bar.

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

2 - Getting started with a 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")
}
Copy code

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 run the first test! 🎉

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

XCT in XCTAssert is a short form 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:

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

XCTAssertNil will check if the expression returns nil and show an error message if it is not nil, XCTAssertTrue will check if the expression returns 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 the test!

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 it's a green tick, a red cross or empty) beside the function name :

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

3 - Unit Test example

In this section, I will show how I added a 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 😂)

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.

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)

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

Since the starting 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'");
}
Copy code

Now that I have the test set up, every time I make 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 for the simulator to load and then spend 10 seconds tapping around 😅.

Afterthought

Although this post doesn't go deep into unit testing, I hope it does answer the question "why should I write a unit test?" for you. It saved me a lot of time and effort in 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 a 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.

No items found.
The Mobile DevOps Newsletter

Explore more topics

App development

Best practices from engineers on how to use Bitrise to build better apps, faster.

Community

Meet other Bitrise engineers, technology experts, power users, partners and join our BUGs.

Company

All the updates about Bitrise events, sponsorships, employees, and more.

Insights

Mobile development, latest tech, industry insights, and interviews with experts.

Mobile DevOps

Learn why mobile development is unique and requires a set of unique practices.

Releases

Stay tuned for the last updates, new features, and product improvements.

The Mobile DevOps Newsletter

Join 1000s of your peers. Sign up to receive Mobile DevOps tips, news, and best practice guides once every two weeks.