Getting started with UI Testing for SwiftUI
Learn how to create user interface tests with the XCTest framework.
30 Dec 2024 · 4 min read
To ensure that our app's user interface behaves correctly during various user actions, iOS provides the XCTest framework for creating UI tests.
In this guide, we'll walk through writing UI tests for a SwiftUI application, including how to query UI elements and interact with them.
Let's jump in.

Introduction to XCTest for UI Testing
XCTest enables developers to write unit tests, performance tests, and UI tests.
When writing UI tests, we simulate user behavior to verify that the app’s interface functions as expected. UI tests help confirm that specific elements are displayed correctly and respond as intended to user actions.
XCTest provides tools to query the view hierarchy and interact with UI elements programmatically, ensuring reliability in our application’s design and functionality.
Preparing for UI Testing
When creating a new SwiftUI application, we can include a UI testing target by selecting the Include Tests option during project setup. This automatically creates a UI testing target in Xcode.
Alternatively, for existing projects, we can manually add a UI testing target by selecting File > New > Target > UI Testing Bundle from Xcode’s menu.
Setting up the Test Class
To write a UI test, we use a Swift class that extends XCTestCase. Xcode creates a sample test file when a UI testing target is added. Below is an example of the generated code:
import XCTestclass 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 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. 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
A test class typically contains the following:
- setUpWithError: This method is called before each test and is used to set up the initial state.
- tearDownWithError: This method runs after each test and can be used for cleanup.
Here’s an example that configures the test environment:
override func setUpWithError() throws {continueAfterFailure = falselet app = XCUIApplication()app.launchArguments = ["isRunningUITests"]app.launch()}
Using launch arguments like "isRunningUITests", we can prepare the app for testing by clearing data, mocking responses, or bypassing login flows.
In our app code, we can access arguments with the ProcessInfo class:
if ProcessInfo.processInfo.arguments.contains("isRunningUITests") {// Prepare application for UI tests.}
Querying UI elements
To interact with UI elements, we use the XCUIApplication class, which provides methods to locate elements in the app’s hierarchy. Elements can be queried by their accessibility identifiers.
For example, to access a button with the identifier loginButton:
let loginButton = app.buttons["loginButton"]
The XCUIElementTypeQueryProvider provides a lot of different queries such as alerts, checkBoxes, collectionViews, datePickers, images, otherElements, scrollViews, searchFields, staticTexts, toolbarButtons etc..
SwiftUI views allow us to assign accessibility identifiers using the accessibilityIdentifier modifier. Here’s an example:
Button("Login", action: {// Login}).accessibilityIdentifier("loginButton")
Simulating user actions
XCTest provides various methods to simulate user actions, such as taps, swipes, and text input. For example:
loginButton.tap()
Other common interaction methods include:
Verifying state in a UI test
To verify whether an expected element is on the screen, we can use the same macros as in unit testing such as XCTAssert and XCTAssertTrue:
XCTAssertTrue(loginButton.exists)
Here’s an example for testing a login screen:
func testLogin() throws {let app = XCUIApplication()app.launchArguments = ["isRunningUITests"]app.launch()let loginButton = app.buttons["loginButton"]XCTAssertTrue(loginButton.waitForExistence(timeout: 2))loginButton.tap()let usernameField = app.textFields["usernameInputField"]XCTAssertTrue(usernameField.waitForExistence(timeout: 2))usernameField.tap()usernameField.typeText("user")let passwordField = app.secureTextFields["passwordInputField"]XCTAssertTrue(passwordField.exists)passwordField.tap()passwordField.typeText("password")let submitButton = app.buttons["submitButton"]XCTAssertTrue(submitButton.exists)XCTAssertTrue(submitButton.isEnabled)submitButton.tap()}
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 provides 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.
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 for the UI tests or to determine which elements require accessibility identifiers.



Newsletter
Like to support my work?
Say hi
Related tags
Articles with related topics
Latest articles and tips



