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 3 of 4 of this article. Click here to view the first page.

Time to Start the Timer

Here’s an example timer cycle a user of the app might choose: work for 8 minutes, take a 2 minute break, work for 8 minutes, take a 2 minute break, then work a final 8 minutes. At this point, you take a longer break and then restart the app when you’re ready.

The parameters for the above example are:

  • Work time: 8 minutes
  • Break time: 2 minutes
  • Repetitions: 3

The next KIF test will enter these parameters, and then tap the “Start Working” button to start the timer. Add the following method to UITests.m, directly below the previous tests you added:

- (void)test20StartTimerAndWaitForFinish {
  [tester tapViewWithAccessibilityLabel:@"Timer"];

  [tester clearTextFromAndThenEnterText:@"Test the timer"
    intoViewWithAccessibilityLabel:@"Task Name"];
  [tester tapViewWithAccessibilityLabel:@"done"];

  [tester setValue:1 forSliderWithAccessibilityLabel:@"Work Time Slider"];
  [tester setValue:50 forSliderWithAccessibilityLabel:@"Work Time Slider"];
  [tester setValue:1 forSliderWithAccessibilityLabel:@"Work Time Slider"];
  [tester setValue:8 forSliderWithAccessibilityLabel:@"Work Time Slider"];

  [tester setValue:1 forSliderWithAccessibilityLabel:@"Break Time Slider"];
  [tester setValue:25 forSliderWithAccessibilityLabel:@"Break Time Slider"];
  [tester setValue:2 forSliderWithAccessibilityLabel:@"Break Time Slider"];
}

Because this test will run immediately after test10PresetTimer (which sets the task name), you can use the clearTextFromAndThenEnterText:intoViewWithAccessibilityLabel: variant rather than plain old enterText:intoViewWithAccessibilityLabel: to clear out any existing text first.

Finally, there are several calls to setValue:forSliderWithAccessibilityLabel:. This is the UISlider specific method to set the new value. Note that the accuracy isn’t always very good. KIF actually simulates the touch event and swipes to set the new value; sometimes the pixel calculations are a little off. But that’s okay, because fingers aren’t all that accurate either!

You only need to set each slider’s value once. The multiple calls are just for kicks, and so you can see KIF changing the value over and over.

Run the tests using Command-U.

The remaining things to test in the UI are the UIStepper control to set the number of repetitions, and the “Start Working” button. The “Start Working” button is easy – you can use tapViewWithAccessibilityLabel: to simulate a tap. But for the UIStepper, we need to take a little detour.

Custom Taps

The UIStepper control has two halves, as shown below, so at this point it’s unclear what will happen if you just call tapViewWithAccessibilityLabel:.

kif-UIStepper

KIF will start out by trying to tap the center of the control. If it’s not a tappable area, it then tries points at the top-left, top-right, bottom-left, and then bottom-right. It turns out that tapping that center border between the plus and minus triggers the plus, so it will increment the stepper.

But what if you wanted to decrement the stepper? There are some workarounds, such as digging into the subviews to find the minus button. The other alternative is to use KIF’s tapScreenAtPoint: test action, which will simulate a tap at any arbitrary point on the screen.

How’s your CGGeometry knowledge? Can you figure out how to calculate the window coordinates of the plus and minus buttons? Ready to prove you’re awesome? Why not try this challenge? It is totally optional, and you can skip ahead to the code with the calculations below. But if you want to test your skills, try to code the calculations before peeking at the answer. By the way, the answer and full explanation are in the solution box.

[Note that you’re going to add this code to the test shortly. This task is just to test your math and CGGeometry skills!]

[spoiler title=”UIStepper Geometry”]
First, you need a reference to the UIStepper:

UIStepper *repsStepper = (UIStepper*)[tester waitForViewWithAccessibilityLabel:@"Reps Stepper"];

Then you can find the center point:

CGPoint stepperCenter = [repsStepper.window convertPoint:repsStepper.center
                                                fromView:repsStepper.superview];

The UIStepper could be inside any number of nested superviews, and what you really want is the center coordinates of the UIStepper relative to the window as a whole, thus the call to convertPoint:fromView:.

Now that you have the center point relative to the window, you can decrease the x-coordinate to get the minus button, or increase it to get the plus button.

CGPoint minusButton = stepperCenter;
minusButton.x -= CGRectGetWidth(repsStepper.frame) / 4;

CGPoint plusButton = stepperCenter;
plusButton.x += CGRectGetWidth(repsStepper.frame) / 4;

If you increase or decrease by one-fourth the width of the UIStepper, that should land you right in the middle of the plus and minus buttons.

Voilà! Two points, representing the center of the plus and minus buttons inside a UIStepper control.
[/spoiler]

Tapping the screen at an arbitrary point is sort of a last resort, but sometimes it is the only way to test your UI. For example, you might have your own custom controls that don’t implement the UIAccessibility Protocol.

Finalizing the Timer

OK, so now you’re at the last steps of this Timer test. Are you realizing the awesome potential of UI testing with KIF? Good!

The final steps are to set the number of repetitions, and then start the timer. Add the following code to the end of test20StartTimerAndWaitForFinish in UITests.m:

  // 1
  UIStepper *repsStepper = (UIStepper*)[tester waitForViewWithAccessibilityLabel:@"Reps Stepper"];
  CGPoint stepperCenter = [repsStepper.window convertPoint:repsStepper.center
                                                  fromView:repsStepper.superview];

  CGPoint minusButton = stepperCenter;
  minusButton.x -= CGRectGetWidth(repsStepper.frame) / 4;

  CGPoint plusButton = stepperCenter;
  plusButton.x += CGRectGetWidth(repsStepper.frame) / 4;

  // 2
  [tester waitForTimeInterval:1];

  // 3
  for (int i = 0; i < 20; i++) {
    [tester tapScreenAtPoint:minusButton];
  }

  [tester waitForTimeInterval:1];
  [tester tapScreenAtPoint:plusButton];
  [tester waitForTimeInterval:1];
  [tester tapScreenAtPoint:plusButton];
  [tester waitForTimeInterval:1];
  
  // 4
  [KIFUITestActor setDefaultTimeout:60];

  // 5
  [tester tapViewWithAccessibilityLabel:@"Start Working"];
  // the timer is ticking away in the modal view...
  [tester waitForViewWithAccessibilityLabel:@"Start Working"];

  // 6
  [KIFUITestActor setDefaultTimeout:10];

Here's what's going to happen in this final phase:

  1. The above code finds coordinates for the plus and minus buttons inside the UIStepper. The explanation for this code is contained in the spoiler box above.
  2. The calls to waitForTimeInterval: add a delay so you can see the stepper value change – otherwise it goes by too fast for human eyes.
  3. The stepper has a maximum value of 20, so tap the minus button 20 times to bring the value back to 1. Then, tap the plus button 2 times (interleaved with more small delays) to bring the number of repetitions to 3, the desired value.
  4. The default timeout for a test step is 10 seconds. Even in accelerated debug mode it's possible for the 20+ minutes of "work" to take longer than 10 seconds, so set the timeout to a more generous number, 60 seconds.
  5. Tap the "Start Working" button, which will bring up the modal view controller. When all the repetitions have passed, the modal view controller will be dismissed. That means you'll be back at the Timer view controller, so waiting for the "Start Working" button to appear again is effectively waiting for the modal view controller to go away.
  6. Reset the timeout back to 10 seconds to clean up.

Once you save the file, you'll see a little diamond-shaped icon next to the method declaration:

Test diamond button

This is a button to run a single test. So rather than firing up the entire test suite, you can run this one method in the test environment. Neat! Click on the diamond to run the test; you should see the simulator start up and then see the test run. Sit back and watch as it enters the task name, the sliders move and the timer ticks away. Without lifting a finger, you're testing the UI.

test20StartTimerAndWaitForFinish

Success! Now you can add successfully setting up tests to manipulate a variety of UI controls to your list of accomplishments.

If you run a single test with the diamond button, it only runs the single method and doesn't call beforeAll at the beginning. If your test depends on beforeAll, you'll still need to run the full test suite.

If you run a single test with the diamond button, it only runs the single method and doesn't call beforeAll at the beginning. If your test depends on beforeAll, you'll still need to run the full test suite.

Greg Heo

Contributors

Greg Heo

Author

Over 300 content creators. Join our team.