Ground truth:
CometChatUIKitSwift5.1.22 +CometChatSDK4.1.7 (symbols verified vscatalogs/ios-v5.json); signatures FETCHED viacometchat-ios-core/references/docs-map.md. The iOS UI Kit docs have no dedicated testing page — this is the pack's own guidance (tracked DOCS GAP). The kit ships prebuilt view controllers/SwiftUI views; do not assert against their internal view hierarchy.
Companion skills (read first)
cometchat-ios-core— the init→login→render lifecycle and the credential flow these tests exercise.
Use this skill when
Adding tests around a CometChat iOS integration, or a "how do I test this" question. NOT part of a normal build — only when tests are explicitly requested (RULES.md → Verification scope).
Decide what you are testing
Test your code, not the kit. Three layers:
- Your logic (token fetch, UID mapping, view-model state, routing) →
XCTestin isolation, keep the SDK off the network. - Your wiring (the chat screen appears after init+login; the right IDs are passed) → a host-app test that drives the launch hook.
- The real round-trip (send → receive) → a thin
XCUITestagainst a test app, not a unit test.
Do not unit-test that the kit's message list renders — that is the kit's own surface.
Keep the SDK off the network in unit tests
Put your CometChat calls behind a protocol your view-models depend on, and inject a fake in tests:
protocol ChatAuthing { func login(authToken: String) async throws -> String } // returns uid
final class FakeChatAuth: ChatAuthing { func login(authToken: String) async throws -> String { "u1" } }
Test your token-fetch, error handling, and UID mapping against FakeChatAuth — no real CometChatUIKit.login, no network, no Auth Key in the test target. Injecting the real implementation only in the app keeps unit tests fast and hermetic.
The init→login gate in host-app tests
The chat screen mounts only after initFromSettings + login resolve (both async). A test that asserts immediately after presenting the screen races the gate — wait for a stable, YOUR-owned element (a title, an accessibility identifier you set), with an expectation/timeout, not a fixed sleep:
let list = app.otherElements["conversationsScreen"] // an identifier YOU set on your container
XCTAssertTrue(list.waitForExistence(timeout: 20))
Set accessibilityIdentifiers on your own containers so UI tests have stable anchors that survive kit updates.
XCUITest smoke
One high-value path against a real test app (seeded users; a test-only Auth Key supplied via the scheme's env, never the release config): launch, sign in, assert the conversation screen appears and a message can be sent. Assert on your accessibility identifiers and visible text — never the kit's internal view tree. Keep it to the happy path + one auth-failure path.
Not worth automating
Kit view internals, exhaustive option matrices, live calls/VoIP push (device- and entitlement-dependent), and screenshot snapshots of kit UI (they churn across kit versions). Spend the budget on your token flow, UID mapping, and the init-gate wiring.
Common pitfalls
- Fixed
sleep()instead ofwaitForExistence— flaky against the async init gate. - Asserting on kit view internals — brittle; anchor on your own
accessibilityIdentifiers. - A real login (and Auth Key) in the unit-test target — hide it behind a protocol + fake; keep secrets out of tests.
- Testing on the release configuration — use a test scheme whose env carries the dev Auth Key, so production stays token-only.
Verify it works
Unit tests pass with a fake auth (no network, no Auth Key in the target) · a host-app/UI test waits out the init gate and finds your identified container · the XCUITest smoke signs in and shows conversations against a test app · no test asserts a kit-internal view.