LV Tutorials: UI Testing in iOS

As of Xcode 7 there is a programmatic interface to our app’s UI, thanks to three new classes: XCUIApplication, XCUIElement and XCUIElementQuery. They allows us to write native UI Tests for our iOS apps.

Unit tests are useful to test a specific case in a class, and make sure that a class works independently. But UI Tests offer us the chance to test classes interactions with each other, user interactions with the app, test the response and behavior of UI elements after such interactions and write integration tests.

About this article

This article explains the basics of UI Testing, a feature we use to test our apps in Lateral View.

It includes an example project, updated for Xcode 8.3.2, Swift 3 and iOS 10.3, plus some tips to include launch variables to configure the app behavior prior running each test and include request stubs to test different networking responses for each request.

You can find the example project here.

Part 1: Create a UI Test Target

To add UI Tests to an existing project, first select the project in the project navigator and then select the + sign at the bottom of the target list (make sure the target list is visible). In the popup menu, scroll down to the Test section and select UI Testing Bundle, then click Next.

Name your new target and click finish.

Part 2: Create a new Scheme

Create a new Scheme to implement a proper configuration for your test and later, see coverage information.

Name your Scheme or leave the default name, and click Ok.

Select Edit Scheme from the drop down list by clicking in the current active scheme name.

Then make sure the option Gather coverage data is selected, you can also select the Shared option to include this scheme as part of the repository.

And make sure the Executable is set to the project target in the Run section of the edit scheme popup.

Part 3: Write an example test

Write a basic test to verify everything is correctly set up. You can find the file created for you in the Project Navigator, under a new group called <ProjectName>UITests , or create your own. In this example, I have deleted the default test and added a file called SignInViewControllerTests.swift

UI tests are easily configured, just create a property from an instance of XCUIApplication.

let app = XCUIApplication()

And set the continueAfterFailure property to false. This indicates to each test case it shouldn’t continue to run after a failure occurs within the test.

continueAfterFailure = false

If you wish, you can write a basic text calling XCTAssert(true) as the only assertion. Remember, for the test to run, it should be a method starting with the word test.

XCTAssert(true)

Run the test selecting your newly created test target, and pressing ⌘+U, and verify everything is working correctly, the simulator should start running your app and the test should pass.

Part 4: Accessing UI Elements

For the first example, I have created a test to verify a native alert is shown when any of the text fields in the Sign In screen of my app are empty at the moment the user clicks the sign in button.

You can start discovering how to access programmatically the different UI elements by clicking the small red “record” button in the Debug Area of the editor.

If you use any static strings file within your app like I do, let’s say a Strings.swift file, you can add it to the UI tests target in the Target Membership section, in the Utilities tab.

That’s it! If an alert is shown containing the static text defined in the Strings.swift file for the empty fields message, the test will pass.

Part 5: Testing networking code with response stubs

In order to emulate different networking response cases for each test, we should use stubs. I use a library called OHHTTPStubs.

I included a helper class with a method that, once called, will catch any request to a specific endpoint and return the stubbed information in a JSON file.

The stub helper provided by the library must receive a condition, in this case a path, to catch the request. In this case we use the function isPath provided by OHHTTPStubs:

private let authenticateUserPath = "/api/users/authenticate"
isPath(authenticateUserPath)

Inside the helper, we return a stub response specifying a JSON file, a status code and the correct Content-Type header.

I added only a stub for a successful sign In response. You can catch a JSON payload to work with using a network proxy like Charles.

The next step is to execute this code in AppDelegate.swift, before any requests are made. But what about the target app? We don’t want it to stub requests responses during production, only at the times we are performing tests.

Well there’s a catch. When working with UI Tests, you should include the stubs library in the application target, and signal the app to start stubbing networking responses during tests using launch arguments.

Launch arguments are strings passed to the target application to enable useful debugging behavior.

I’ve set two launch arguments in the Strings file so I can later load them at the setup of the tests from the UI Tests target. And then evaluate them in the app target at runtime. If they are present, the app will stub the request. So every Sign In test starts without any users signed in. The two launch arguments are deleteUserDefaultsFlag and networkStubsFlag:

struct Flags {
   static let deleteUserDefaultsFlag = "deleteUserDefaults"
   static let networkStubsFlag = "networkStubs"
}

If you want to test multiple Sign In cases, and you save session information in user defaults you can also use launch arguments to delete user defaults information.

Here’s what the evaluation looks in AppDelegate.swift, the flag for stubbing networking data is evaluated and if present, the application delegate calls the helper function I defined earlier:

Finally I’ve written a test to see if the app goes to the home screen after a successful Sign In, notice the flag setup calling:

app.launchArguments.append(<STRING FLAG>)

Part 6: Checking out code coverage information

After running your tests, you can verify code coverage data by selecting the Report Navigator icon in the Navigator Section and selecting the last test run under your app’s UI Tests target. And finally, selecting the Coverage Tab:

You can also verify which portions of code you tested and which ones you didn’t, by activating the code coverage highlighting under the Edit option, with Xcode Open, in MacOS menu bar. And selecting Show Code Coverage.

At the right of the code you can se a small number indicating how many times the app passed through that portion of code during the test run. In the image below I’m using that feature to check if the app loaded the Sign In stub:

Now you’re ready to start adding UI Tests to your projects and begin enjoying the benefits of automated integration tests. Good luck!


If you want to know more about technology and innovation, check us out at Lateral View or subscribe to our newsletter!