Logo for tanaschita.com

Getting started with UI testing for SwiftUI

Learn how to create user interface tests with the XCTest framework.

30 May 2022 · 4 min read

To make sure that our app's user interface behaves correctly when certain actions are performed, iOS offers the possibility to create UI tests by using the XCTest framework.

In this guide, we are going to look at how to write UI tests for a SwiftUI application.

Basics

The XCTest framework allows us to create unit tests, performance tests and UI tests for an Xcode project. When writing UI tests, we are actually interacting with our app instead of performing tests against a certain API like we would do with unit tests.

UI tests basically recreate a user's behaviour und check if expected elements are on the screen when certain actions are performed. To be able to do that, the XCTest framework provides us with methods and properties to query for views in the view hierarchy and to interact with them in a way a user would do.

Let's look at how to write a UI test step by step.

Getting started

Before starting writing UI tests, we need a UI testing target. When creating a new SwiftUI application, we can select the Include Tests checkbox and Xcode will create the target for us.

Adding a test target.
Adding a test target.

Alternatively, we can add a UI testing target to an existing project by selecting File > New > Target > UI Testing bundle in Xcode's menu.

Running the first UI test

To start writing UI tests, we need a Swift class that extends XCTestCase. Xcode created an example one for us as we added the UI testing target.

import XCTest
class ExampleAppUITests: XCTestCase {
override func setUpWithError() throws {
continueAfterFailure = false
}
override func tearDownWithError() throws {
}
func testExample() throws {
let app = XCUIApplication()
app.launch()
}
}

Xcode runs every method that starts with the word test as a separate test. We can already try out and run the created testExample method which simply launches the app.

We can either run all tests by selecting Product > Test or run a specific test with the play button next to the test method name.

Running a test.
Running a test.

If the test target compiles without errors and the testExample gets a green checkmark, we are ready to go.

The anatomy of a XCTestCase test class

As we have seen in the example above, the test class overrides the setUpWithError and the tearDownWithError method.

The setUpWithError method is called before the invocation of each test method in the class. It's a good place to set the initial state for every test. With the continueAfterFailure property, we can decide whether a test method should continue running after a failure occurs.

The tearDownWithError method is called after each test method in the class.

Interacting with the application

We interact with our application by using the XCUIApplication class.

We already saw XCUIApplication in action to launch the app. Here, we have the possibility to pass additional information to the application to be able to prepare the application for testing, for example to clear or prefill persisted data or to setup mocking server requests.

let app = XCUIApplication()
app.launchArguments = ["isRunningUITests"]
app.launch()

Which we can access with the ProcessInfo class:

if ProcessInfo.processInfo.arguments.contains("isRunningUITests") {
// Prepare application for UI tests.
}

Querying UI elements

XCUIApplication provides properties we can use to query and interact with UI elements.

For example, the buttons query provides access to buttons. We can access a specific button by using a subscript syntax passing in a String which is either an accessibility label or an accessibility identifier.

let loginButton = app.buttons["loginButton"]

Checkout XCUIElementTypeQueryProvider to look up other available queries. Some examples are alerts, checkBoxes, collectionViews, datePickers, images, otherElements, scrollViews, searchFields, staticTexts, toolbarButtons.

UI elements like labels or buttons have their titles as accessibility labels out of the box. Although accessibility identifiers must be set manually for every view, using them instead ensures that the UI tests will not break due to localization or from text changes.

Every SwiftUI view provides a modifier to set the accessibilityIdentifier:

Button(LocalizedStringKey("loginButtonTitle"), action: {
// Login
})
.accessibilityIdentifier("loginButton")

Simulating user actions

The XCTest framework provides methods to simulate actions like button taps, textfield inputs, swipes etc.

loginButton.tap()

Some other examples of interaction methods are doubleTap(), scroll(), swipeUp(), swipeRight() pinch(withScale:velocity:), typeText(_:) etc. Checkout the XCUIElement class for more.

Verifying state in a UI test

To verify whether an expected element is on the screen, we can use the same macros we would use in unit testing with the XCTest framework like XCTAssert, XCTAssertTrue etc.

XCTAssertTrue(loginButton.exists)

Knowing all that, we could write a UI test to test a login screen that looks as follows:

func testLogin() throws {
let app = XCUIApplication()
app.launchArguments = ["isRunningUITests"]
app.launch()
let timeout = 2
let loginButton = app.buttons["loginButton"]
XCTAssertTrue(loginButton.waitForExistence(timeout: timeout))
loginButton.tap()
let usernameInputField = app.textFields["usernameInputField"]
let passwordInputField = app.textFields["passwordInputField"]
let loginRequestButton = app.textFields["loginRequestButton"]
XCTAssertTrue(usernameInputField.waitForExistence(timeout: timeout))
XCTAssertTrue(passwordInputField.waitForExistence(timeout: timeout))
XCTAssertTrue(loginRequestButton.waitForExistence(timeout: timeout))
XCTAssertFalse(loginRequestButton.isEnabled)
usernameInputField.tap()
usernameInputField.typeText("tanaschita")
passwordInputField.tap()
passwordInputField.typeText("tanaschitasPassword1234!")
XCTAssertTrue(loginRequestButton.isEnabled)
}

The waitForExistence(timeout:) method assures we don't get any test failures due to animations where views might need some time to appear on the screen.

Recording a UI test

Xcode also offers the possibility to record a UI test.

For that, click inside a UI test method and select Editor > Start recording UI test. The app will start and generate UI testing code for us as we interact with the app.

The recording might be helpful in some cases and is fun to play around with, but the generated code will need readjustments and improvements in most cases, so it might be more productive to just directly writing it down.

Accessibility Inspector

A helpful tool when it comes to UI testing is the Accessibility Inspector. Open it by selecting Xcode > Open Developer Tool > Accessibility Inspector in Xcode's menu.

Accessibility Inspector.

The Accessibility Inspector shows us different accessibility values when we select a UI element in our app. We can use it to get the accessibility identifiers we want to use in our UI tests or to find out which ones are still missing.

Newsletter

Like to support my work?

Say hi

Related tags

Articles with related topics

Latest articles and tips