iOS UI Testing with KIF

Testing UI in iOS apps is an important topic, especially as apps become more complex. Learn how to do iOS UI testing with KIF in this tutorial. By Greg Heo.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

A Simple Test: Tapping Around

The app has a standard tab bar controller layout with a UINavigationController inside each of the three tabs. To warm up for the next exercise, you’ll determine if:

  • The storyboard is wired up properly
  • The tabs display the correct views.

Tab bar buttons automatically set accessibility labels to be the same as their text label, so KIF will be able to find the History, Timer, and Settings tab bar buttons with the labels “History”, “Timer”, and “Settings”.

The History tab has a table view that shows all the tasks performed with the timer. Open HistoryViewController.m from the solanum group and add these lines to the end of viewDidLoad:

[self.tableView setAccessibilityLabel:@"History List"];
[self.tableView setIsAccessibilityElement:YES];

This will set the table view’s accessibility label, so that KIF can find it. Usually, a table view is only accessible if it’s empty. If there are table view cells they’re a more likely target, so the table view itself will hide from the accessibility API. Essentially, the accessibility API assumes, by default, that the table view isn’t important. This is likely the case in terms of accessibility, but if you want to reference the table view in KIF then it needs to be accessible as well. The setIsAccessibilityElement: call ensures the table view is always accessible, regardless of its contents.

Depending on the app, accessible non-empty table views can actually make things more difficult if for users who use the accessibility features (e.g. VoiceOver). In your own apps, you can wrap lines of code between #ifdef DEBUG and #endif directives so they’re only compiled into your debug builds. The DEBUG preprocessor macro is pre-defined in Xcode’s project templates.

Depending on the app, accessible non-empty table views can actually make things more difficult if for users who use the accessibility features (e.g. VoiceOver). In your own apps, you can wrap lines of code between #ifdef DEBUG and #endif directives so they’re only compiled into your debug builds. The DEBUG preprocessor macro is pre-defined in Xcode’s project templates.

The Timer tab has several controls you could look for, but the “Task Name” text field is conveniently at the top of the view. Rather than set the label through code, open Main.storyboard and find the Timer View Controller view. Select the task name text field.

Timer view in storyboard

Open the Utilities panel if it isn’t already up — and select the Identity Inspector. Hint: it’s the third icon from the left, or use the keyboard shortcut ‘⌥ ⌘ 3’.

Utilities and Storyboard Inspector

Under Accessibility in the inspector, enter “Task Name” in the Label field. Stay sharp now, because accessibility labels are case-sensitive. Be sure to enter that exactly as shown with a capital T and N!

Storyboard accessibility

The Settings tab has already been set up the views with accessibility labels, so you’re all set to move to the next step!

In your own projects, you’ll need to continue to fill in the accessibility labels from code or in Interface Builder as you’ve done here. For your convenience, the rest of sample app’s accessibility labels are already set.

In your own projects, you’ll need to continue to fill in the accessibility labels from code or in Interface Builder as you’ve done here. For your convenience, the rest of sample app’s accessibility labels are already set.

Back in UITests.m, add this method after beforeAll:

- (void)test00TabBarButtons {
  // 1
  [tester tapViewWithAccessibilityLabel:@"History"];
  [tester waitForViewWithAccessibilityLabel:@"History List"];

  // 2
  [tester tapViewWithAccessibilityLabel:@"Timer"];
  [tester waitForViewWithAccessibilityLabel:@"Task Name"];

  // 3
  [tester tapViewWithAccessibilityLabel:@"Settings"];
  [tester waitForViewWithAccessibilityLabel:@"Debug Mode"];
}

The test runner will look for all methods that start with the word “test” at runtime, and then run them in alphabetical order. This method starts with the name “test00” so that it will run before the tests you’ll add later, because those names will start with “test10”, “test20”, etc.

Each of the three parts of the method will perform a similar set of actions: tap on a tab bar button, and check for the expected view to show on the screen. 10 seconds is the default timeout for waitForViewWithAccessibilityLabel:. If the view with the specified accessibility label doesn’t show itself during that timeframe, the test will fail.

Run the tests by going to Product\Test or hitting Command-U. You’ll see the beforeAll steps which will clear the History, and then test00TabBarButtons will take over and switch to the History, Timer and Settings tabs in sequence.

kif-triptych

Well, what do you know? You just wrote and ran an interface test, and saw your little app “drive” itself! Congrats! You’re on your way to mastering automated UI testing.

User Input

Sure, switching tabs is nifty, but it’s time to move on to more realistic actions: entering text, triggering modal dialogs and selecting a table view row.

The test app has some built-in presets that will change the work time, break time and number of repetitions to a set of recommended values. If you’re curious to see their definitions, have a look at presetItems in PresetsViewController.m.

Selecting a preset could be a test of its own, but that action is more efficient when it is a part of other tests. In this case, it’s worth splitting it out into a helper method.

Add the following method to the implementation block of UITests.m:

- (void)selectPresetAtIndex:(NSInteger)index {
  [tester tapViewWithAccessibilityLabel:@"Timer"];

  [tester tapViewWithAccessibilityLabel:@"Presets"];
  [tester tapRowInTableViewWithAccessibilityLabel:@"Presets List"
    atIndexPath:[NSIndexPath indexPathForRow:index inSection:0]];

  [tester waitForAbsenceOfViewWithAccessibilityLabel:@"Presets List"];
}

The first step here is to switch to the Timer tab so that you’re in the right place. Then tap the Presets button in the navigation bar. When the “Presets List” table view appears, tap on the row at the specified index.

Tapping on the row will dismiss the view controller, so use waitForAbsenceOfViewWithAccessibilityLabel: to ensure it vanishes before you continue.

Did you notice that this method doesn’t start with the word test? The test runner won’t automatically run it. Instead, you’ll manually call the method from within your own tests.

Now, add the following test method to UITests.m:

- (void)test10PresetTimer {
  // 1
  [tester tapViewWithAccessibilityLabel:@"Timer"];

  // 2
  [tester enterText:@"Set up a test" intoViewWithAccessibilityLabel:@"Task Name"];
  [tester tapViewWithAccessibilityLabel:@"done"];

  // 3
  [self selectPresetAtIndex:1];

  // 4
  UISlider *slider = (UISlider *)[tester waitForViewWithAccessibilityLabel:@"Work Time Slider"];
  STAssertEqualsWithAccuracy([slider value], 15.0f, 0.1, @"Work time slider was not set!");
}

KIF test actions have very readable names; see if you can figure out what’s going on here. Think of it as a…test! Yes, of course the pun is intentional. :]

OK, here’s what’s happening section by section:

  1. Switch to the Timer tab.
  2. Enter “Set up a test” into the text field with the “Task Name” accessibility label (remember, you added this label to the storyboard earlier). Tap the “Done” button to close the keyboard.
  3. Call the helper method to select the second preset.
  4. Selecting the preset should change the slider values, so make sure it has indeed changed to the correct value.

In the final section of code, you’ll find a handy trick: waitForViewWithAccessibilityLabel:. Not only will it wait for the view to appear, but it actually returns a pointer to the view itself! Here, you cast the return value to UISlider to match up the proper type.

Since KIF test cases are also regular OCUnit test cases, you can call the standard STAssert assertion macros. Assertions are run-time checks that cause the test to fail if some condition isn’t met. The simplest assertion is STAssertTrue, which will pass if the parameter passed in is true.

STAssertEquals will check that the first two parameters are equal. The slider value is a float, so be careful about matching up types. Thus, the 15.0f appears in the assertion. You also need to be careful about small inaccuracies in floating point representations. This is because floating point values cannot necessarily be stored 100% accurately. 15.0 might end up actually being stored as 15.000000000000001 for example. So STAssertEqualsWithAccuracy is a better choice; its third parameter is the allowed variance. In this case, if the values are within +/- 0.1 of each other, the assertion will still pass.

Run the tests using Command-U. You should now see three sequences: beforeAll clears the history, test00TabBarButtons switches through each tab, and then your latest masterpiece in test10PresetTimer will enter a task name and select a preset.

test10PresetTimer

Another successful test! At this point, your test mimics users by tapping all kind of things and even typing on the keyboard, but there’s even more to come!

Greg Heo

Contributors

Greg Heo

Author

Over 300 content creators. Join our team.