agentleFS
MathiasPaulenkoMathiasPaulenko/ai-toolkit/skills/xctest-ios/SKILL.md

xctest-ios

Skill2 starsChanged 3 months agoMITView on GitHub

Native iOS testing with XCTest: unit tests, UI automation, performance, snapshot testing, parallel execution, and CI/CD with Xcode Cloud.

  • Installs packages
Files
SKILL.md

XCTest iOS

Invoke when user asks about XCTest, iOS testing, Swift unit tests, or native iOS automation.

Core Principles

  • First-party: Apple's framework, no third-party dependencies.
  • Unified: Unit, UI, and performance tests in one framework.
  • Tooling: Xcode integration, parallel testing, code coverage, CI with Xcode Cloud.

Project Setup

// In Xcode: File > New > Target > UI Testing Bundle
// Or add test target to Package.swift

// Package.swift
targets: [
  .target(name: "MyApp"),
  .testTarget(
    name: "MyAppTests",
    dependencies: ["MyApp"]
  ),
]

Unit Tests (XCTestCase)

import XCTest
@testable import MyApp

final class CalculatorTests: XCTestCase {
  var calculator: Calculator!

  override func setUp() {
    super.setUp()
    calculator = Calculator()
  }

  override func tearDown() {
    calculator = nil
    super.tearDown()
  }

  func testAddition() {
    XCTAssertEqual(calculator.add(2, 3), 5)
  }

  func testDivisionByZeroThrows() {
    XCTAssertThrowsError(try calculator.divide(10, 0)) { error in
      XCTAssertEqual(error as? CalculatorError, .divisionByZero)
    }
  }

  // Async test
  func testFetchUser() async throws {
    let user = try await api.fetchUser(id: 1)
    XCTAssertEqual(user.name, "Alice")
  }

  // Parameterized with enum
  func testSquares() {
    let cases = [(input: 2, expected: 4), (input: 3, expected: 9)]
    for c in cases {
      XCTAssertEqual(calculator.square(c.input), c.expected)
    }
  }
}

UI Tests (XCUIApplication)

import XCTest

final class LoginUITests: XCTestCase {
  let app = XCUIApplication()

  override func setUp() {
    continueAfterFailure = false
    app.launch()
  }

  func testSuccessfulLogin() {
    let emailField = app.textFields["emailTextField"]
    let passwordField = app.secureTextFields["passwordTextField"]
    let loginButton = app.buttons["loginButton"]
    let welcomeLabel = app.staticTexts["welcomeLabel"]

    emailField.tap()
    emailField.typeText("alice@test.com")

    passwordField.tap()
    passwordField.typeText("password123")

    loginButton.tap()

    XCTAssertTrue(welcomeLabel.waitForExistence(timeout: 5))
    XCTAssertEqual(welcomeLabel.label, "Welcome, Alice")
  }

  func testInvalidPasswordShowsError() {
    app.textFields["emailTextField"].tap()
    app.textFields["emailTextField"].typeText("alice@test.com")

    app.secureTextFields["passwordTextField"].tap()
    app.secureTextFields["passwordTextField"].typeText("wrong")

    app.buttons["loginButton"].tap()

    let errorAlert = app.alerts["Error"]
    XCTAssertTrue(errorAlert.waitForExistence(timeout: 3))
  }
}

Performance Tests

func testTableScrollPerformance() throws {
  measure(
    metrics: [
      XCTClockMetric(),
      XCTCPUMetric(),
      XCTMemoryMetric(),
    ]
  ) {
    let table = app.tables.element(boundBy: 0)
    table.swipeUp(velocity: .fast)
    table.swipeDown(velocity: .fast)
  }
}

Snapshot Testing (Point-Free)

// Package.swift: .package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.15.0")

import SnapshotTesting
import XCTest

func testLoginScreen() {
  let view = LoginView()
  let controller = UIHostingController(rootView: view)
  
  assertSnapshot(
    of: controller,
    as: .image(on: .iPhone13Pro)
  )
}

Parallel Test Execution

# xcodebuild with parallel testing
xcodebuild test \
  -project MyApp.xcodeproj \
  -scheme MyApp \
  -destination 'platform=iOS Simulator,name=iPhone 15' \
  -parallel-testing-enabled YES \
  -maximum-parallel-testing-workers 4

CI/CD (Xcode Cloud)

# ci_post_clone.sh
#!/bin/bash
brew install swiftlint
swiftlint lint --reporter json > swiftlint.json
# ci_post_xcodebuild.sh
#!/bin/bash
# Upload coverage to external service
bash <(curl -s https://codecov.io/bash) -J 'MyApp'

Accessibility Testing

func testAccessibilityLabels() {
  let emailField = app.textFields["emailTextField"]
  XCTAssertEqual(
    emailField.label,
    "Email Address"
  )
}

Anti-Patterns

Anti-PatternFix
sleep(3) in UI testsUse waitForExistence(timeout:)
Accessing views by indexUse accessibilityIdentifier
Testing only on iPhoneAdd iPad targets
No tearDownClean state between tests
Hardcoded device names in CIUse xcrun simctl list dynamically

Quick Reference

APIPurpose
XCUIApplication().launch()Launch app
.tap()Tap element
.typeText("text")Type text
.swipeUp() / .swipeDown()Scroll
.pinch(withScale: 2)Pinch zoom
.twoFingerTap()Two-finger tap
.waitForExistence(timeout:)Wait for element
XCTAssertTrue / XCTAssertEqualAssertions

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

Reviews can't be read right now.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.