iOS Automation using XCUITest

NITHIN B
6 min readSep 6, 2024

--

UI tests allow us to verify that when we change parts of our app’s data model, the view controllers, views, and controls respond appropriately. We can also create test cases to manipulate the app’s views and controls, as if a person were interacting with the interface.

Here is how XCUITest stands out as a robust testing solution:

  • Native and faster than many other popular functional test frameworks.
  • Managed by Apple.
  • Enhance the accessibility of iOS apps.
  • No need to install anything separately.
  • Includes XCode test recorder.

What are XCTest & XCUITest:

XCTest is a framework integrated into Xcode, enabling the writing of tests for iOS apps without additional dependencies.

XCUITest, launched by Apple in 2015, is a UI testing framework that allows the creation of automated tests for iOS apps using Swift or Objective-C.

Adding a UI Test Bundle target to a project:

  • Open the project navigator and select your project. Look for the plus (+) icon at the bottom and click on it.
  • In the “Choose a template for your new target” window, scroll down to find and select “UI Testing Bundle”.
  • Provide a name for the UI Test target and configure any necessary options.
  • After setting up the target, click “Finish” to create UI Test Bundle target.

XCUITest APIs:

The XCUITest APIs form a powerful framework that allows you to automate user interface tests on iOS apps. These APIs enable you to perform UI interactions and validate app behavior seamlessly, making your testing process robust and efficient. Let’s explore some XCUITest APIs that can help us achieve this goal.

  1. XCUIApplication:

This API represents the instance of the application being tested. It provides access to the app’s UI elements and allows actions such as launching, monitoring, and terminating the app.

let app = XCUIApplication()
app.launch() // Launching the application

Example:

let app = XCUIApplication()
app.launch()
app.wait(for: .runningForeground, timeout: 10)
let app = XCUIApplication()
if app.state == .runningForeground {
// App is active
}

These methods provide powerful tools for controlling the lifecycle and behavior of the app during UI testing, making it easier to simulate user interactions and verify outcomes.

2. XCUIElement:

XCUIElement represents various UI elements within an iOS application. These elements include buttons, labels, text fields and more, providing methods to facilitate interactions with them during UI testing.

3. XCUIElementQuery:

XCUIElementQuery is a crucial component of the XCUITest framework that allows you to search for and interact with user interface elements within an iOS application. It provides a way to define criteria for finding specific UI elements, such as buttons, labels, text fields, or any other controls, within the app’s view hierarchy.

  1. Querying elements: XCUIElementQuery enables you to create queries that search for UI elements based on specific attributes like their type, identifier, label or other properties.
  2. Chaining Queries: Queries can be chained to narrow down the search. This allows for precise targeting of elements within complex UI structures. For instance, you can first query for all the tables in a view and then refine the query to find a specific cell within a particular table.
  3. Resolving Queries: The query itself does not immediately find the element. Instead , it describes a search that will be executed when you interact with the query result.
  4. Methods of XCUIElementQuery:
    element ( boundBy: )
    : Returns the element at the specified index in the query result. This is useful when there are multiple matching elements and you need to select one.
    element ( matching: ) : Returns the first element matching the specified criteria. You can combine it with other query elements to target specific elements.
    count : Returns the number of elements that match the query. This can be useful for assertions to verify the presence or absence of elements.
    allElementsBoundByIndex : Returns all elements matching the query in an array, allowing you to iterate over them or access them by index.

Example usage:

let app = XCUIApplication()
app.launch()

// Query for a button with the identifier "submitButton"
let submitButton = app.buttons["submitButton"]

// Verify that the button exists and then tap it
if submitButton.exists {
submitButton.tap()
}

// Query for all text fields in a view
let textFields = app.textFields
let firstTextField = textFields.element(boundBy: 0)
firstTextField.tap()
firstTextField.typeText("Sample Text")

// Assert that there are exactly two text fields in the view
XCTAssertEqual(textFields.count, 2)

4. XCUIElement Actions

XCUIElement Actions refer to the various interactions that can be performed on user interface elements within an iOS application using the XCUITest framework. The XCUIElement class provides a range of methods that simulate user actions like tapping, typing, swiping and more. These actions allow users to interact with and test the behavior of the app’s UI elements during automated tests.

5. XCUIElement Attributes:

XCUIElement are properties associated with user interface elements in an iOS application that provide information about the element’s state, identity and characteristics. These attributes help in identifying and interacting with UI elements during automated testing using the XCUITest framework.

XCTestCase class:

The XCTestcase class is a fundamental component of the XCUITest framework, which is used for writing and executing unit tests and UI tests. XCTestCase serves as the base class for all test cases, providing the necessary structure and methods to define and run tests.

Key Features of XCTestCase:

  1. Test Methods:

Each test method is defined in a subclass of XCTestCase and should begin with the word “test”, which allows the Xcode testing environment to automatically recognise it as a test case.

class MyTests: XCTestCase {
func testAddition() {
let result = 2 + 2
XCTAssertEqual(result, 4, "Addition result should be 4")
}
}

2. Assertions:

XCTestCase provides various assertion methods to verify conditions during tests. These include:

  • XCTAssert: Checks if a condition is true.
  • XCTAssertEqual: Checks if two values are equal.
  • XCTAssertNotNil: Checks if an object is not nil
  • XCTAssertThrowsError: Checks if an expression throws an error.
func testDivision() {
let result = 10 / 2
XCTAssertEqual(result, 5, "Division result should be 5")
}

3. Setup and Teardown Methods:

XCTestCase provides special methods to set up and tear down conditions before and after each test, ensuring a clean test environment.
Set() : Called before each test method in the class.
tearDown() : Called after each test method in the class.

override func setUp() {
super.setUp()
// Code to set up the environment for tests
}

override func tearDown() {
// Code to clean up after tests
super.tearDown()
}

4. Performance testing:

XCTestCase allows for performance testing by measuring the time taken to execute a block of code using the ‘measure’ method.

func testPerformanceExample() {
measure {
// Code to measure the performance of
}
}

5. Test Case Lifecycle:

Tests in XCTestCase are executed in a specific order: setup(), then the test method, and finally tearDown(). This ensures that each test runs in isolation with a fresh setup.

6. Test Expectations:

XCTestCase can handle asynchronous code using expectations. You can create an expectation and wait for it to be fulfilled, which is useful for testing asynchronous operations like network requests.

func testAsyncOperation() {
let expectation = self.expectation(description: "Async Operation")
performAsyncTask {
expectation.fulfill() // Mark the expectation as fulfilled when the task is done
}


waitForExpectations(timeout: 5, handler: nil) // Wait for the expectation to be fulfilled
}

Example of an XCTestCase Class:

import XCTest

class MathTests: XCTestCase {

override func setUp() {
super.setUp()
// Initialization code here
}

override func tearDown() {
// Cleanup code here
super.tearDown()
}

func testAddition() {
let result = 3 + 2
XCTAssertEqual(result, 5, "Expected 3 + 2 to equal 5")
}

func testPerformanceOfMultiplication() {
measure {
_ = (1...1000).reduce(1, *)
}
}
}

--

--

No responses yet