How to test with Mock Device Kit on iOS

Updated: May 14, 2026

Overview

Use this guide when your iOS project already integrates the Wearables Device Access Toolkit and you need to test without physical glasses.

Set up Mock Device Kit in XCTest

Create a reusable base rule or test class that configures Mock Device Kit, grants permissions, and resets state.
import XCTest
import MetaWearablesDAT

@MainActor
class MockDeviceKitTestCase: XCTestCase {
    private var mockDevice: MockRaybanMeta?
    private var cameraKit: MockCameraKit?

    override func setUp() async throws {
        try await super.setUp()

        try? Wearables.configure()
        mockDevice = MockDeviceKit.shared.pairRaybanMeta()
        cameraKit = mockDevice?.getCameraKit()
    }

    override func tearDown() async throws {
        MockDeviceKit.shared.pairedDevices.forEach { device in
            MockDeviceKit.shared.unpairDevice(device)
        }
        mockDevice = nil
        cameraKit = nil
        try await super.tearDown()
    }
}

Configure camera feeds for streaming tests

Mock camera feeds let you verify streaming and capture workflows without video hardware.

Provide a mock video feed

guard let device = MockDeviceKit.shared.pairRaybanMeta() else { return }
let camera = device.getCameraKit()
await camera.setCameraFeed(fileURL: videoURL)

Provide a captured photo

guard let device = MockDeviceKit.shared.pairRaybanMeta() else { return }
let camera = device.getCameraKit()
await camera.setCapturedImage(fileURL: imageURL)

MockDevice test server for XCUITest

The MockDevice test server enables UI automation testing by allowing XCUITest processes to control mock devices at runtime via HTTP. This eliminates the need to hardcode mock device setup in your app, enabling dynamic test scenarios where each test can configure its own device state.
This approach enables UI automation without physical hardware (run XCUITests on CI simulators), dynamic test scenarios (each test pairs different devices and configures unique camera feeds), full flow testing (registration, pairing, streaming, photo capture), deterministic testing (control exact device state to test edge cases), and parallel test execution (each test manages its own mock device independently).
Note: For more examples of how to leverage the MockDevice Test Server to test your apps, see the Camera Access sample code at ExternalSampleApps/CameraAccess/CameraAccessUITests/.

Architecture

The test server runs in your app process and listens on localhost. The XCUITest client connects via HTTP to control mock devices. The server writes its port to a temp file that the client reads to discover the connection details. Both processes share the same /tmp directory on the simulator.
┌─────────────────────────────────────┐                         ┌──────────────────────────────────┐
│         App Process (DEBUG)         │     HTTP (localhost)    │     XCUITest Process             │
│                                     │                         │                                  │
│  MockDeviceKit                      │   POST /device/pair     │  MockDeviceTestClient            │
│   └─ startTestServer()              │   POST /device/don      │                                  │
│                                     │   POST /device/doff     │  Port discovery:                 │
│                                     │   POST /device/power-on │   reads port from temp file      │
│                                     │   GET  /device/state    │   written by server on start     │
│                                     │   POST /camera/set-feed │                                  │
│                                     │   ...                   │                                  │
│                                     │◄────────────────────────│                                  │
└─────────────────────────────────────┘                         └──────────────────────────────────┘

Server setup (app process)

Start the test server when the app launches with a --ui-testing flag. The server should only be enabled in DEBUG builds.
import MWDATMockDevice

if ProcessInfo.processInfo.arguments.contains("--ui-testing") {
    MockDeviceKit.shared.enable(config: MockDeviceKitConfig(initiallyRegistered: false))

    let portFilePath = ProcessInfo.processInfo.environment["MWDAT_TEST_SERVER_PORT_FILE"]
    Task {
        try await MockDeviceKit.shared.startTestServer(portFilePath: portFilePath)
        // Server is now listening — tests drive mock device setup via HTTP
    }
}

Client setup (XCUITest process)

Link MWDATMockDeviceTestClient in your UI test target. The client reads the port from the temp file.
import MWDATMockDeviceTestClient
import XCTest

final class MyUITests: XCTestCase {
    var portFilePath: String {
        NSTemporaryDirectory() + "mwdat_test_server_port.txt"
    }
    private let app = XCUIApplication()
    private var mockClient: MockDeviceTestClient!
    private var deviceId: String?

    override func setUpWithError() throws {
        // Remove any stale port file from a previous run
        try? FileManager.default.removeItem(atPath: portFilePath)

        app.launchArguments = ["--ui-testing"]
        app.launchEnvironment["MWDAT_TEST_SERVER_PORT_FILE"] = portFilePath
        app.launch()

        // Initialize the client after launch so the server has time to write the port file
        mockClient = MockDeviceTestClient(portFilePath: portFilePath)
        XCTAssertTrue(mockClient.waitForServer(timeout: 10), "Test server should be running")
    }

    override func tearDownWithError() throws {
        if let deviceId {
            mockClient.unpairDevice(deviceId: deviceId)
        }
        deviceId = nil
    }

    func testStreaming() {
        // Pair a device — pairDevice() powers it on and dons it automatically
        guard let id = mockClient.pairDevice() else {
            XCTFail("Failed to pair mock device")
            return
        }
        deviceId = id

        // Configure camera resources for this device
        mockClient.setCameraFeed(deviceId: id, resourceName: "plant", ext: "mp4")
        mockClient.setCapturedImage(deviceId: id, resourceName: "plant", ext: "png")

        // Device is now paired, powered on, and donned
        let startButton = app.buttons["Start streaming"]
        XCTAssertTrue(startButton.waitForExistence(timeout: 15))

        // Control device state mid-test
        mockClient.doff(deviceId: id)   // device becomes inactive
        mockClient.don(deviceId: id)    // device becomes active again
        mockClient.fold(deviceId: id)   // hinges close, streaming stops
        mockClient.unfold(deviceId: id) // hinges open

        // Query device state
        let state = mockClient.getDeviceState()
        XCTAssertEqual(state?["pairedDeviceCount"] as? Int, 1)
    }
}

Available client methods

MethodDescription
pairDevice()
Pairs a mock Ray-Ban Meta, powers on, dons; returns the device ID (nil on failure)
unpairDevice(deviceId:)
Unpairs the specified mock device
powerOn(deviceId:) / powerOff(deviceId:)
Controls device power state
don(deviceId:) / doff(deviceId:)
Simulates wearing / removing the device
fold(deviceId:) / unfold(deviceId:)
Simulates folding / unfolding the glasses
captouchTap(deviceId:)
Simulates a single tap gesture (toggles pause/resume)
captouchTapAndHold(deviceId:)
Simulates tap-and-hold (stops active session)
setCameraFeed(deviceId:resourceName:ext:)
Sets the camera feed video resource from the test bundle
setCapturedImage(deviceId:resourceName:ext:)
Sets the captured image resource from the test bundle
getDeviceState()
Returns {"pairedDeviceCount": Int, "deviceIds": [String]}
healthCheck()
Checks if the server is reachable
waitForServer(timeout:)
Polls until the server responds or timeout expires

Swift Package Manager setup

Add MWDATMockDeviceTestClient as a dependency of your UI test target. The client has no dependency on MWDATMockDevice — it communicates purely over HTTP.