Skip to content

Learn

Waiting in XCTest: A detailed guide

Learn how to handle waits in XCTest using expectations and XCTWaiter—and scale stable iOS test automation faster with Tricentis Testim Mobile.

waiting in xct test

Introduction

Modern mobile applications rely heavily on asynchronous behavior—network calls, UI transitions, background processes, and system notifications all happen outside a linear execution flow. Ensuring that these interactions behave reliably across devices is critical to delivering high-quality iOS applications.

While Apple’s XCTest framework provides developers with powerful tools to handle asynchronous testing through waits and expectations, managing these workflows at scale can quickly become complex. This is where Tricentis Testim Mobile complements native XCTest capabilities by enabling faster, more stable, and scalable mobile test automation—whether you’re a developer writing Swift-based tests or a QA professional seeking low-code or no-code solutions.

In this article, we’ll walk through different ways to implement waits in XCTest, with practical examples. Along the way, you’ll see how these foundational concepts align seamlessly with the intelligent synchronization and AI-powered stability offered by Tricentis Testim Mobile.

Understanding XCTest and waits

XCTest is a testing framework developed by Apple that allows developers to test their iOS and macOS applications. It supplies tools to help write and run automated tests, ensuring the quality of the code before developers release it to the public. In addition, XCTest provides a simple and intuitive way for developers to write unit tests for their code, helping to catch bugs early on and prevent them from becoming more significant problems down the road.

In automated tests, developers often need to wait for certain conditions to be met before making an assertion or continuing with the test. This is where the concept of waits comes into play.

Waits allow you to pause a test’s execution until a specific condition is met. This is especially useful when:

  1. Waiting for a UI element to appear
  2. Waiting for animations to finish
  3. Waiting for a network request to complete
  4. Waiting for system notifications

In this post, we’ll explain different ways to implement waits in XCTest tests, covering:

  1. Failure and timeout handling
  2. Multiple expectations
  3. Expecting something not to happen
  4. Waiting for notifications

If you’re new to Xcode, Swift, or XCTest, it’s recommended to review introductory XCTesting resources before diving deeper.

Using the XCTestExpectation class

The most common way to implement waits in XCTest is by using the XCTestExpectation class. An XCTestExpectation represents a condition you expect to be fulfilled during the test execution.

Here’s an example of how to use an XCTestExpectation to wait for a network request to complete:

// Create expectation
let expectation = XCTestExpectation(description: "Wait for network request")

// Make the network request
makeNetworkRequest { result in
    expectation.fulfill()
}

// Wait for the expectation to be fulfilled
wait(for: [expectation], timeout: 10)

// Continue with the rest of the test
XCTAssertEqual(result, expectedResult)

In this example, we create an expectation with a description of “Wait for a network request.” We then make a network request and fulfill the expectation when the request has been completed. Finally, we use the wait(for:timeout:) method to wait for the expectation to be fulfilled. The test will pause its execution until the expectation has been fulfilled or until the timeout of 10 seconds has passed.

Using the XCTWaiter class

The second method for implementing waits in XCTest tests is using the XCTWaiter class. The XCTWaiter class provides a more flexible and customizable way to wait for specific conditions to be met than the XCTestExpectation condition.

Here’s an example of how to use the XCTWaiter class to wait for a UI element to appear:

let waiter = XCTWaiter()

let result = waiter.wait(
    for: [
        XCTNSPredicateExpectation(
            predicate: NSPredicate(format: "exists == 1"),
            object: element
        )
    ],
    timeout: 10
)

switch result {
case .completed:
    XCTAssertTrue(element.isHittable)
case .timedOut:
    XCTFail("Element did not appear within the timeout")
default:
    break
}

Here:

  • The test waits until the element exists
  • The result determines whether the test continues or fails

This conditional logic reflects the same challenges that Tricentis Testim Mobile addresses by automatically synchronizing UI states across devices and OS versions.

Failure and timeout parameters

Both XCTestExpectation and XCTWaiter rely on timeout values. Choosing the right timeout is essential:

  • Too short → flaky tests
  • Too long → slow feedback loops

Timeouts should be aligned with expected system performance. Tricentis Testim Mobile helps mitigate these issues by dynamically adapting waits using AI-driven analysis of application behavior.

Waiting for multiple expectations

Some scenarios require multiple asynchronous operations to complete before proceeding.

When using XCTestExpectation, you can create multiple expectations and wait for all of them to be fulfilled:

Example with XCTestExpectation

// Create expectations
let expectation1 = XCTestExpectation(description: "Wait for network request 1")
let expectation2 = XCTestExpectation(description: "Wait for network request 2")
// Make the first network request
makeNetworkRequest1 { result in
    // Fulfill the first expectation
    expectation1.fulfill()
}
// Make the second network request
makeNetworkRequest2 { result in
    // Fulfill the second expectation
    expectation2.fulfill()
}
// Wait for both expectations to be fulfilled
wait(for: [expectation1, expectation2], timeout: 10)
// Continue with the rest of the test
XCTAssertEqual(result1, expectedResult1)
XCTAssertEqual(result2, expectedResult2)

Example with XCTWaiter

let waiter = XCTWaiter()

let result = waiter.wait(
    for: [
        XCTNSPredicateExpectation(predicate: NSPredicate(format: "exists == 1"), object: element1),
        XCTNSPredicateExpectation(predicate: NSPredicate(format: "exists == 1"), object: element2)
    ],
    timeout: 10
)

switch result {
case .completed:
    XCTAssertTrue(element1.isHittable)
    XCTAssertTrue(element2.isHittable)
case .timedOut:
    XCTFail("Elements did not appear within the timeout")
default:
    break
}

In this example, we use the wait(for:timeout:) method to wait for two elements to exist. The method takes an array of XCTExpectations, and we use the XCTNSPredicateExpectation class to wait for the elements to exist. The XCTWaiter will wait for both expectations to be fulfilled or until the timeout of 10 seconds has passed.

Once the XCTWaiter has completed, we check the result to determine what happened. If the result is .completed, it means that both elements exist, and we can continue with the rest of the test. On the other hand, if the result is .timedOut, it means one of the elements did not appear within the timeout, and the test will fail.

Expecting something not to happen

In addition to waiting for something to happen, you can also use XCTestExpectation and XCTWaiter to wait for something not to happen. For example, you may want to wait for an error message to disappear from the screen—in other words, wait for there not to be a message.

To do this, you can create an expectation for the error message to disappear and use the XCTWaiter to wait for the expectation to be fulfilled:

// Create expectation
let expectation = XCTestExpectation(description: "Wait for error message to disappear")
// Wait for the error message to disappear
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
    errorMessage.isHidden = true
    expectation.fulfill()
}
// Waif for the expectation to be fulfilled
wait(for: [expectation], timeout: 10)
// Continue with the rest of the test
XCTAssertTrue(errorMessage.isHidden)

In this example, we create an expectation for the error message to disappear. We then use DispatchQueue.main.asyncAfter to fulfill the expectation after five seconds. The XCTWaiter will then wait for the expectation to be fulfilled or until the timeout of 10 seconds has passed.

Notifications

In some cases, you may need to wait for a notification to be posted before continuing with the test. For example, you may need to wait for a network request to complete and post a notification with the result.

To do this, you can create an expectation for the notification to be posted and use the XCTWaiter to wait for the expectation to be fulfilled:

// Create expectation
let expectation = XCTestExpectation(description: "Wait for network request to complete")
// Observe the notification
let notificationObserver = NotificationCenter.default.addObserver(forName: .networkRequestDidComplete, object: nil, queue: nil) { _ in
    expectation.fulfill()
}
// Perform the network request
networkManager.fetchData()
// Wait for the notification to be posted
wait(for: [expectation], timeout: 10)
// Remove the observer
NotificationCenter.default.removeObserver(notificationObserver)
// Continue with the rest of the test
XCTAssertTrue(networkManager.data != nil)

In this example, we create an expectation for the .networkRequestDidComplete notification to be posted. We then observe the notification using the addObserver() method of the NotificationCenter class. When the notification is posted, the expectation is fulfilled.

Next, we perform the network request and wait for the notification to be posted using the wait(for:timeout:) method. Finally, we remove the observer using the removeObserver() method and continue with the rest of the test. These will confirm that the network request was successful and that data was returned.

Conclusion

Waiting correctly in XCTest is essential for building reliable and maintainable iOS test suites. By mastering XCTestExpectation and XCTWaiter, you can handle asynchronous events such as UI rendering, network calls, and notifications with confidence.

However, as applications grow more complex, maintaining these workflows manually can become time-consuming and costly. This is where Tricentis Testim Mobile provides a powerful advantage. By combining AI-driven test stability, intelligent waits, and support for both coded and codeless testing, Tricentis Testim Mobile helps teams reduce flakiness, accelerate releases, and focus on innovation rather than test maintenance.

If you’re looking to scale your mobile testing efforts without increasing complexity, Tricentis Testim Mobile offers a modern, efficient approach to mobile test automation—built to work alongside frameworks like XCTest, not replace them.

Author:

Guest Contributors

Date: Jan. 19, 2026
Author:

Guest Contributors

Date: Jan. 19, 2026

You may also be interested in...