Network-backed UI paths need deterministic data before they can become useful XCUITest coverage. A mock boundary inside the app keeps login, profile, checkout, or empty-state screens testable when the real API is slow, unavailable, rate-limited, or unsafe for repeat test runs.

The UI test process cannot intercept URLSession traffic in the app process by itself. Pass a launch environment value with XCUIApplication, let the app read that value during startup, and configure the app's network client to use a test-only URLProtocol fixture for the API host.

Keep the mock path narrow enough that an unmocked endpoint fails visibly instead of falling through to production. The test should pass only when the app renders data from the fixture response and writes its result bundle for later failure review.

Steps to use network mocks in XCUITest tests:

  1. Choose the app request that the UI test must control.
    Mock trigger: UITEST_NETWORK_MOCKS=enabled
    Mocked host: api.example.com
    Fixture path: /profile
    Expected UI state: profile-name shows Fixture User

    Use a host and path owned by the app's API client. Do not point the production base URL at a local test server unless that is already the app's supported test mode.

  2. Add a fixture protocol to the app target.
    FixtureURLProtocol.swift
    import Foundation
     
    struct Profile: Decodable {
        let name: String
        let plan: String
    }
     
    final class FixtureURLProtocol: URLProtocol {
        override class func canInit(with request: URLRequest) -> Bool {
            request.url?.host == "api.example.com"
        }
     
        override class func canonicalRequest(for request: URLRequest) -> URLRequest {
            request
        }
     
        override func startLoading() {
            let path = request.url?.path ?? "/"
            let statusCode: Int
            let body: Data
     
            switch path {
            case "/profile":
                statusCode = 200
                body = Data(#"{"name":"Fixture User","plan":"Test"}"#.utf8)
            default:
                statusCode = 500
                body = Data(#"{"error":"missing fixture"}"#.utf8)
            }
     
            let response = HTTPURLResponse(
                url: request.url!,
                statusCode: statusCode,
                httpVersion: "HTTP/1.1",
                headerFields: ["Content-Type": "application/json"]
            )!
     
            client?.urlProtocol(self, didReceive: response, cacheStoragePolicy: .notAllowed)
            client?.urlProtocol(self, didLoad: body)
            client?.urlProtocolDidFinishLoading(self)
        }
     
        override func stopLoading() {}
    }

    The class belongs in the app target because the app process owns the network session. The fallback response makes an unexpected API path fail instead of reaching the real service.

  3. Route the app's API client through the fixture only when the launch environment enables it.
    APIClient.swift
    import Foundation
     
    struct APIClient {
        let session: URLSession
     
        static func make() -> APIClient {
            let configuration = URLSessionConfiguration.default
     
            if ProcessInfo.processInfo.environment["UITEST_NETWORK_MOCKS"] == "enabled" {
                configuration.protocolClasses = [FixtureURLProtocol.self]
            }
     
            return APIClient(session: URLSession(configuration: configuration))
        }
     
        func loadProfile() async throws -> Profile {
            let url = URL(string: "https://api.example.com/profile")!
            let (data, _) = try await session.data(from: url)
            return try JSONDecoder().decode(Profile.self, from: data)
        }
    }

    Keep the fixture branch behind a test-only environment value. Shipping code should create the normal URLSession configuration when UITEST_NETWORK_MOCKS is absent.

  4. Add a UI test that launches the app with network mocks enabled.
    ProfileNetworkMockUITests.swift
    import XCTest
     
    final class ProfileNetworkMockUITests: XCTestCase {
        private var app: XCUIApplication!
     
        override func setUpWithError() throws {
            continueAfterFailure = false
            app = XCUIApplication()
            app.launchEnvironment["UITEST_NETWORK_MOCKS"] = "enabled"
            app.launch()
        }
     
        func testProfileScreenUsesFixtureResponse() throws {
            app.buttons["profile-refresh"].tap()
     
            let profileName = app.staticTexts["profile-name"]
            XCTAssertTrue(
                profileName.waitForExistence(timeout: 5),
                "Profile name did not appear"
            )
            XCTAssertEqual(profileName.label, "Fixture User")
        }
    }

    The app must expose stable accessibility identifiers for the refresh control and rendered profile label.
    Related: How to wait for UI elements in XCUITest

  5. Create a directory for the test result bundle.
    $ mkdir -p TestResults
  6. Run the network-mock UI test on an iOS Simulator.
    $ xcodebuild test \
      -workspace MyApp.xcworkspace \
      -scheme MyApp \
      -destination 'platform=iOS Simulator,name=iPhone 16,OS=latest' \
      -only-testing:MyAppUITests/ProfileNetworkMockUITests \
      -resultBundlePath TestResults/NetworkMocks.xcresult
    Test Suite 'Selected tests' started.
    Test Case '-[MyAppUITests.ProfileNetworkMockUITests testProfileScreenUsesFixtureResponse]' started.
    Test Case '-[MyAppUITests.ProfileNetworkMockUITests testProfileScreenUsesFixtureResponse]' passed (4.018 seconds).
    ** TEST SUCCEEDED **

    Use -project MyApp.xcodeproj instead of -workspace MyApp.xcworkspace when the app does not use a workspace.
    Related: How to run XCUITest with xcodebuild in CI

  7. Confirm that xcodebuild wrote the result bundle.
    $ ls -d TestResults/NetworkMocks.xcresult
    TestResults/NetworkMocks.xcresult

    Open the bundle with open TestResults/NetworkMocks.xcresult when a failure needs screenshots, logs, or the selected test report.
    Related: How to save XCUITest result bundles