Cometchat ios testing
Skill cometchat/cometchat-skills/skills/cometchat-ios-testing
Add CometChat chat & messaging and voice & video calls to any React, Next.js, React Native, Angular, Android, iOS, or Flutter project through your AI coding agent. Works with Claude Code, Cursor, Codex, VS Code Copilot, Windsurf, Cline, Kiro, and 50+ more agents.
npx -y skills add cometchat/cometchat-skills --skill cometchat-ios-testingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
What its author says it does
Copied from the file, not written here
Testing patterns for CometChat iOS UI Kit v5 in Swift / SwiftUI / UIKit projects. Covers XCTest setup, mocking CometChatSDK + CometChatUIKitSwift via protocols, snapshot testing with iOSSnapshotTestCase, async/await test helpers, UI testing for full chat flows on simulators, and CI on Xcode Cloud / GitHub Actions.
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
12.2 KB, ~2.9k tokens by cl100k_base, as published. Nobody here has run it
Purpose
Testing patterns for iOS CometChat integrations. XCTest (built-in) for unit + integration; SnapshotTesting (Pointfree) for visual regression; XCUITest for full-flow UI tests on the simulator.
Read these other skills first:
cometchat-ios-core— init, login, Secrets patterncometchat-ios-components— what each VC does (tests assert against this)cometchat-ios-calls/references/callkit-and-pushkit.md— CallKit testing strategy (different from chat)
Ground truth: Official docs: https://www.cometchat.com/docs/ui-kit/ios/overview · Docs MCP: claude mcp add --transport http cometchat-docs https://www.cometchat.com/docs/mcp (or fetch the URL directly without MCP).
- XCTest — https://developer.apple.com/documentation/xctest
- SnapshotTesting — https://github.com/pointfreeco/swift-snapshot-testing
1. Test target structure
A typical iOS project has three targets:
| Target | When |
|---|---|
YourApp | Production code |
YourAppTests | Unit + integration tests (XCTest) |
YourAppUITests | UI tests (XCUITest, runs on simulator) |
The skill writes to YourAppTests for most assertions; YourAppUITests for full flows.
2. Mocking the SDK via protocols
The CometChat SDKs are class-based with static methods (CometChat.init, CometChat.login). Hard to mock directly. Wrap them in protocols you own:
// Protocols/CometChatService.swift
import CometChatSDK
// CometChatException does NOT conform to Swift's Error, so a throwing
// continuation can't resume with it directly — bridge it through this.
struct CometChatTestError: Error { let message: String }
protocol CometChatServiceProtocol {
func initialize(appId: String, region: String) async throws
func login(uid: String, authKey: String) async throws -> User
func logout() async throws
func getLoggedInUser() -> User?
func sendMessage(_ message: TextMessage) async throws -> TextMessage
}
// Production implementation
final class CometChatService: CometChatServiceProtocol {
func initialize(appId: String, region: String) async throws {
try await withCheckedThrowingContinuation { (cont: CheckedContinuation<Void, Error>) in
let settings = AppSettings.AppSettingsBuilder()
.subscribePresenceForAllUsers()
.setRegion(region: region)
.build()
CometChat.init(appId: appId, appSettings: settings, onSuccess: { _ in
cont.resume()
}, onError: { error in
cont.resume(throwing: CometChatTestError(message: error.errorDescription))
})
}
}
func login(uid: String, authKey: String) async throws -> User {
try await withCheckedThrowingContinuation { (cont: CheckedContinuation<User, Error>) in
CometChat.login(UID: uid, authKey: authKey, onSuccess: { cont.resume(returning: $0) },
onError: { cont.resume(throwing: CometChatTestError(message: $0.errorDescription)) })
}
}
// ... etc
}
Inject the protocol via DI:
final class ChatViewController: UIViewController {
private let service: CometChatServiceProtocol
init(service: CometChatServiceProtocol = CometChatService()) {
self.service = service
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) { fatalError() }
}
Test with a mock:
// Tests/CometChatServiceMock.swift
final class CometChatServiceMock: CometChatServiceProtocol {
var initializeCallCount = 0
var initializeError: Error?
var loginUser: User?
var loginError: Error?
func initialize(appId: String, region: String) async throws {
initializeCallCount += 1
if let error = initializeError { throw error }
}
func login(uid: String, authKey: String) async throws -> User {
if let error = loginError { throw error }
return loginUser ?? User(uid: uid, name: "Test User")
}
func logout() async throws {}
func getLoggedInUser() -> User? { loginUser }
func sendMessage(_ message: TextMessage) async throws -> TextMessage { message }
}
3. XCTest patterns
Async/await tests
import XCTest
@testable import YourApp
final class ChatViewControllerTests: XCTestCase {
func testInitializeRunsBeforeLogin() async throws {
let mock = CometChatServiceMock()
let vc = ChatViewController(service: mock)
await vc.bootstrapCometChat()
XCTAssertEqual(mock.initializeCallCount, 1)
XCTAssertNotNil(vc.currentUser)
}
func testLoginFailureSurfacesError() async throws {
let mock = CometChatServiceMock()
mock.loginError = NSError(domain: "Test", code: 401, userInfo: [NSLocalizedDescriptionKey: "Unauthorized"])
let vc = ChatViewController(service: mock)
await vc.bootstrapCometChat()
XCTAssertEqual(vc.errorMessage, "Unauthorized")
XCTAssertEqual(vc.errorLabel?.textColor, .red) // skill rule: error UI must be visible
}
}
Expectation-based tests (legacy / non-async code)
func testMessageSendFiresCallback() {
let expectation = expectation(description: "message sent")
let mock = CometChatServiceMock()
let vc = ChatViewController(service: mock)
vc.send(message: "Hello") { _ in
expectation.fulfill()
}
wait(for: [expectation], timeout: 1.0)
}
For SDK callback-based code, prefer wrapping in protocol + async/await per Section 2; this pattern is for legacy code only.
4. SwiftUI view tests
import SwiftUI
import XCTest
@testable import YourApp
@MainActor
final class ProfileViewTests: XCTestCase {
func testCallButtonInvokesService() throws {
let mock = CallServiceMock()
let view = ProfileView(user: testUser(), callService: mock)
let host = UIHostingController(rootView: view)
host.loadViewIfNeeded()
// Hard to interact with SwiftUI views from XCTest directly — use ViewInspector or UI tests
// For most behavioral tests, prefer XCUITest (Section 6)
}
}
Pure SwiftUI testing is hard; pragmatic choice is to keep most tests at the service / view-model layer and put view assertions in UI tests.
ViewInspector (third-party)
For SwiftUI views you do want to inspect from XCTest, install ViewInspector:
import ViewInspector
func testProfileShowsUserName() throws {
let user = testUser()
let view = ProfileView(user: user)
let text = try view.inspect().find(text: user.name)
XCTAssertNotNil(text)
}
Adds a runtime dep. Not strictly necessary if you have UI tests.
5. Snapshot testing
# Package.swift dependency
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing.git", from: "1.15.0")
import SnapshotTesting
import XCTest
@testable import YourApp
final class ChatViewSnapshotTests: XCTestCase {
func testEmptyChatView() {
let view = ChatView(messages: [])
let host = UIHostingController(rootView: view)
assertSnapshot(matching: host, as: .image(on: .iPhone13))
}
func testChatViewWithMessages() {
let messages = [
TextMessage(receiverUid: "alice", text: "Hi", receiverType: .user),
TextMessage(receiverUid: "bob", text: "Hey", receiverType: .user),
]
let view = ChatView(messages: messages)
let host = UIHostingController(rootView: view)
assertSnapshot(matching: host, as: .image(on: .iPhone13))
}
}
First run creates the reference image; subsequent runs diff against it. Commit the __Snapshots__ directory.
Gotcha: snapshot tests are sensitive to font rendering — if CI uses a different macOS version than dev, snapshots won't match. Pin runners to a known version.
6. UI tests (XCUITest)
import XCTest
final class ChatUITests: XCTestCase {
override func setUp() {
continueAfterFailure = false
let app = XCUIApplication()
app.launchEnvironment["UI_TEST_MODE"] = "1"
app.launchEnvironment["TEST_UID"] = "cometchat-uid-1"
app.launch()
}
func testLoginAndSeeConversations() {
let app = XCUIApplication()
XCTAssertTrue(app.staticTexts["Welcome"].waitForExistence(timeout: 10))
app.buttons["Messages"].tap()
XCTAssertTrue(app.staticTexts["Conversations"].waitForExistence(timeout: 10))
}
func testSendMessage() {
let app = XCUIApplication()
app.buttons["Messages"].tap()
app.buttons["cometchat-uid-2"].tap()
let composer = app.textFields["Type a message"]
composer.tap()
composer.typeText("Hello from UI test")
app.buttons["Send"].tap()
XCTAssertTrue(app.staticTexts["Hello from UI test"].waitForExistence(timeout: 5))
}
}
UI_TEST_MODE flag: in production code, check this env var and skip animations / use shorter timeouts:
if ProcessInfo.processInfo.environment["UI_TEST_MODE"] == "1" {
UIView.setAnimationsEnabled(false)
}
Test users: UI tests need real login. Use the dev cometchat-uid-1 through cometchat-uid-5; never use production credentials.
7. CI configuration
GitHub Actions
name: tests
on: [push, pull_request]
jobs:
unit:
runs-on: macos-13 # pin macOS version for snapshot stability
steps:
- uses: actions/checkout@v4
- run: |
xcodebuild test \
-scheme YourApp \
-destination "platform=iOS Simulator,name=iPhone 15,OS=17.0" \
-only-testing:YourAppTests \
COMETCHAT_TEST_APP_ID="${{ secrets.TEST_COMETCHAT_APP_ID }}" \
COMETCHAT_TEST_REGION="${{ secrets.TEST_COMETCHAT_REGION }}" \
COMETCHAT_TEST_AUTH_KEY="${{ secrets.TEST_COMETCHAT_AUTH_KEY }}"
ui:
runs-on: macos-13
needs: unit
steps:
- uses: actions/checkout@v4
- run: |
xcodebuild test \
-scheme YourApp \
-destination "platform=iOS Simulator,name=iPhone 15,OS=17.0" \
-only-testing:YourAppUITests \
COMETCHAT_TEST_APP_ID="${{ secrets.TEST_COMETCHAT_APP_ID }}"
Xcode Cloud
Configure in App Store Connect → Xcode Cloud. Use the same env var convention; Xcode Cloud injects them into xcodebuild automatically.
8. Anti-patterns
- Mocking CometChat directly (without the protocol wrapper). The static methods aren't swappable; you'd need OCMock and method swizzling. The protocol wrapper is cleaner.
- Using real WebSocket connections in unit tests. They're flaky on CI. Mock the service layer.
- Snapshot tests on a Mac with system font preference different from CI. Pin macOS + Xcode versions.
- Hardcoding
cometchat-uid-1strings across many tests. Centralize via aTestUsersenum so renames are one-edit. - Skipping
continueAfterFailure = falsein UI tests. Default UI tests continue running after a failure, hiding root causes in cascade failures. - Storing test credentials in
Secrets.swift. Use env vars passed viaxcodebuildand read viaProcessInfo.processInfo.environment["..."]in test bundles only.
9. Verification checklist
-
CometChatServiceProtocol(or similar) wrapping the SDK; production VCs use it via DI - Mock implementation in
Tests/Mocks/ - At least one test for "init resolves before VC is functional"
- At least one test for "error UI shows on init/login failure"
- Snapshot tests for at least the empty + populated states of chat surfaces
- At least one UI test for login + see conversations
- Test target uses dedicated CometChat test App ID via env vars (not Secrets.swift)
- CI pinned to macOS + Xcode versions
- No
continueAfterFailure = truein UI tests
10. Pointers
cometchat-ios-calls/references/callkit-and-pushkit.md— calls + CallKit testingcometchat-ios-core— init, login, Secrets patterns the tests assert againstcometchat-ios-troubleshooting— when tests pass but production breaks