swift-testing
Swift testing patterns: Swift Testing framework (Swift 6+), XCTest for UI tests, async/await test cases, actor testing, Combine testing, and XCUITest for UI automation. TDD for Swift/SwiftUI.
Best use case
swift-testing is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Swift testing patterns: Swift Testing framework (Swift 6+), XCTest for UI tests, async/await test cases, actor testing, Combine testing, and XCUITest for UI automation. TDD for Swift/SwiftUI.
Teams using swift-testing should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/swift-testing/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How swift-testing Compares
| Feature / Agent | swift-testing | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Swift testing patterns: Swift Testing framework (Swift 6+), XCTest for UI tests, async/await test cases, actor testing, Combine testing, and XCUITest for UI automation. TDD for Swift/SwiftUI.
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# Swift Testing
Core testing patterns for Swift using the Swift Testing framework, XCTest, and XCUITest.
## When to Activate
- Writing tests for Swift or SwiftUI code
- Setting up a test target in Xcode
- Testing async/actor code
- Writing UI automation tests
- Achieving 80%+ code coverage in Swift projects
- Migrating an existing XCTest suite to the Swift Testing framework (`@Test`, `#expect`)
- Debugging flaky tests caused by shared mutable state or fixed `sleep` waits
- Adding parameterized test cases to cover multiple input combinations efficiently
## Framework Selection
| Use Case | Framework | Notes |
|---|---|---|
| Unit + Integration | **Swift Testing** | Swift 6+, preferred. `@Test`, `#expect`, `@Suite` |
| Unit + Integration (legacy) | **XCTest** | Compatible with all Swift versions |
| UI Automation | **XCUITest** | Xcode Instruments + accessibility identifiers |
| Protocol-based mocking | See `swift-protocol-di-testing` | Companion skill for DI patterns |
## Swift Testing Framework (Swift 6+)
```swift
import Testing
// Basic test
@Test func formatPrice() {
#expect(PriceFormatter.format(cents: 1000, currency: "USD") == "$10.00")
}
// Grouped suite
@Suite("PriceFormatter")
struct PriceFormatterTests {
@Test func formatsZero() {
#expect(PriceFormatter.format(cents: 0, currency: "USD") == "$0.00")
}
@Test func throwsOnNegative() throws {
#expect(throws: PriceError.negativeAmount) {
try PriceFormatter.format(cents: -1, currency: "USD")
}
}
}
// Parameterized tests
@Test("Formats various currencies", arguments: [
(100, "USD", "$1.00"),
(100, "EUR", "€1.00"),
(100, "GBP", "£1.00"),
])
func formatsMultipleCurrencies(cents: Int, currency: String, expected: String) {
#expect(PriceFormatter.format(cents: cents, currency: currency) == expected)
}
```
## Async Testing
```swift
import Testing
@Test func fetchesUser() async throws {
let service = UserService(client: MockHTTPClient())
let user = try await service.fetchUser(id: "123")
#expect(user.id == "123")
#expect(user.name == "Test User")
}
// Timeout-bounded async tests
@Test(.timeLimit(.seconds(5)))
func completesWithinTimeout() async throws {
let result = try await slowOperation()
#expect(result != nil)
}
```
## Actor Testing
```swift
actor Counter {
private(set) var count = 0
func increment() { count += 1 }
}
@Test func actorIncrements() async {
let counter = Counter()
await counter.increment()
let count = await counter.count
#expect(count == 1)
}
```
## XCTest (Legacy / UI Tests)
```swift
import XCTest
class UserServiceTests: XCTestCase {
var sut: UserService!
var mockClient: MockHTTPClient!
override func setUp() {
super.setUp()
mockClient = MockHTTPClient()
sut = UserService(client: mockClient)
}
override func tearDown() {
sut = nil
mockClient = nil
super.tearDown()
}
func testFetchUser_returnsUser() async throws {
mockClient.stubbedResponse = .success(UserFixtures.testUser)
let user = try await sut.fetchUser(id: "123")
XCTAssertEqual(user.id, "123")
}
func testFetchUser_throwsOnNetworkError() async {
mockClient.stubbedResponse = .failure(URLError(.notConnectedToInternet))
do {
_ = try await sut.fetchUser(id: "123")
XCTFail("Expected error to be thrown")
} catch {
XCTAssertTrue(error is URLError)
}
}
}
```
## Combine Testing
```swift
import XCTest
import Combine
class ViewModelTests: XCTestCase {
var cancellables = Set<AnyCancellable>()
func testPublishesUpdatedValue() {
let vm = CounterViewModel()
var received: [Int] = []
let expectation = expectation(description: "receives values")
expectation.expectedFulfillmentCount = 3
vm.$count.sink { value in
received.append(value)
expectation.fulfill()
}.store(in: &cancellables)
vm.increment()
vm.increment()
waitForExpectations(timeout: 1)
XCTAssertEqual(received, [0, 1, 2])
}
}
```
## UI Testing with XCUITest
```swift
import XCTest
class LoginUITests: XCTestCase {
let app = XCUIApplication()
override func setUpWithError() throws {
continueAfterFailure = false
app.launchArguments = ["--uitesting", "--reset-state"]
app.launch()
}
func testLoginFlow() throws {
// Use accessibilityIdentifier set in SwiftUI with .accessibilityIdentifier()
let emailField = app.textFields["login.email"]
let passwordField = app.secureTextFields["login.password"]
let loginButton = app.buttons["login.submit"]
emailField.tap()
emailField.typeText("user@example.com")
passwordField.tap()
passwordField.typeText("password123")
loginButton.tap()
XCTAssertTrue(app.staticTexts["dashboard.title"].waitForExistence(timeout: 3))
}
}
```
## Coverage in Xcode
```bash
# Enable code coverage in scheme settings:
# Product → Scheme → Edit Scheme → Test → Code Coverage → ✓ Gather coverage
# Run with coverage from CLI
xcodebuild test \
-scheme MyApp \
-destination 'platform=iOS Simulator,name=iPhone 16' \
-enableCodeCoverage YES | xcpretty
```
Target: **80%+ line coverage** — enforce in CI via `xccov` or `slather`.
## TDD Cycle for Swift
1. **RED**: Write `@Test func behaviour() { #expect(...) }` — it fails to compile or returns wrong value
2. **GREEN**: Write minimum Swift code to make `#expect` pass
3. **REFACTOR**: Extract protocols, use value types, clean up — all tests stay green
4. **VERIFY**: Run `swift test --enable-code-coverage`, check report
## Anti-Patterns
### Force-Unwrapping in Tests
**Wrong:**
```swift
func testFetchUser_returnsUser() async throws {
let user = try await sut.fetchUser(id: "123")
XCTAssertEqual(user!.id, "123") // crashes on nil, hides the real failure
}
```
**Correct:**
```swift
func testFetchUser_returnsUser() async throws {
let user = try await sut.fetchUser(id: "123")
XCTAssertEqual(user.id, "123") // throws and reports properly on failure
}
```
**Why:** Force-unwrapping in tests causes a crash instead of a test failure, hiding the root cause and making CI output unreadable.
### Using `sleep` to Wait for Async Results
**Wrong:**
```swift
@Test func publishesEvent() async {
sut.triggerAsyncWork()
try await Task.sleep(for: .seconds(1)) // arbitrary wait, brittle
#expect(sut.didPublish == true)
}
```
**Correct:**
```swift
@Test func publishesEvent() async throws {
let didPublish = try await withTimeout(.seconds(5)) {
await sut.triggerAsyncWork()
return sut.didPublish
}
#expect(didPublish == true)
}
```
**Why:** Fixed sleeps make tests slow and still flaky; use structured concurrency with bounded waits instead.
### Testing Implementation Details Instead of Behavior
**Wrong:**
```swift
@Test func incrementUpdatesPrivateCount() {
let vm = CounterViewModel()
vm.increment()
#expect(vm._internalCount == 1) // accesses private state
}
```
**Correct:**
```swift
@Test func incrementUpdatesDisplayedValue() {
let vm = CounterViewModel()
vm.increment()
#expect(vm.displayText == "1") // tests observable output
}
```
**Why:** Tests coupled to private implementation break on safe refactors; test observable behavior instead.
### Sharing Mutable State Between Tests
**Wrong:**
```swift
var sharedService = UserService(client: MockHTTPClient())
@Test func firstTest() async throws {
sharedService.reset()
// ... test A modifies sharedService
}
@Test func secondTest() async throws {
// relies on sharedService state from firstTest — order-dependent
}
```
**Correct:**
```swift
@Suite("UserService")
struct UserServiceTests {
@Test func fetchUser() async throws {
let service = UserService(client: MockHTTPClient()) // fresh instance
// test A
}
@Test func handleError() async throws {
let service = UserService(client: MockHTTPClient()) // fresh instance
// test B
}
}
```
**Why:** Shared mutable state causes order-dependent flakiness; create fresh instances per test.
### Using UI Label Text Instead of Accessibility Identifiers in XCUITest
**Wrong:**
```swift
func testLoginFlow() throws {
app.buttons["Log In"].tap() // breaks if button label is localized or renamed
}
```
**Correct:**
```swift
func testLoginFlow() throws {
app.buttons["login.submit"].tap() // stable accessibilityIdentifier set in SwiftUI
}
```
**Why:** Querying by label text breaks with localization changes or copy rewrites; `accessibilityIdentifier` is stable and locale-independent.
## Common Pitfalls
- **Avoid `@MainActor` in test bodies**: Marks the entire test synchronous — use `await` instead
- **Always set `continueAfterFailure = false`** in UI tests to stop on first failure
- **Use `accessibilityIdentifier` not label text** for stable UI test element queries
- **Prefer `#expect` over `XCTAssert*`** in Swift Testing — better error messages and parameterizationRelated Skills
visual-testing
Visual Regression Testing: tool comparison (Chromatic/Percy/Playwright screenshots/BackstopJS), pixel-diff vs AI-based comparison, baseline management, flakiness strategies (masks, tolerances, waitForLoadState), CI integration with GitHub Actions, and Storybook integration.
typescript-testing
TypeScript testing patterns: Vitest for unit/integration, Playwright for E2E, MSW for API mocking, Testing Library for React components. Core TDD methodology for TypeScript/JavaScript projects.
swiftui-patterns
SwiftUI architecture patterns, state management with @Observable, view composition, navigation, performance optimization, and modern iOS/macOS UI best practices.
swift-protocol-di-testing
Protocol-based dependency injection for testable Swift code — mock file system, network, and external APIs using focused protocols and Swift Testing.
swift-patterns
Core Swift patterns — value vs reference types, protocols, generics, optionals, Result, error handling, Codable, and module organization. Foundation for all Swift development.
swift-patterns-advanced
Advanced Swift patterns — property wrappers, result builders, Combine basics, opaque & existential types, macro system, advanced generics, and performance optimization. Extends swift-patterns.
swift-concurrency-6-2
Swift 6.2 Approachable Concurrency — single-threaded by default, @concurrent for explicit background offloading, isolated conformances for main actor types.
swift-actor-persistence
Thread-safe data persistence in Swift using actors — in-memory cache with file-backed storage, eliminating data races by design.
scala-testing
Scala testing with ScalaTest, MUnit, and ScalaCheck: FunSpec/FlatSpec test structure, property-based testing with forAll, mocking with MockitoSugar, Cats Effect testing with munit-cats-effect (runTest/IOSuite), ZIO Test, Testcontainers-Scala for database integration tests, and CI integration with sbt. Use when writing or reviewing Scala tests.
rust-testing
Rust testing patterns — unit tests with mockall, integration tests with sqlx transactions, HTTP handler testing (axum), benchmarks (criterion), property tests (proptest), fuzzing, and CI with cargo-nextest.
rust-testing-advanced
Advanced Rust testing anti-patterns and corrections — cfg(test) placement, expect() over unwrap(), mockall expectation ordering, executor mixing (#[tokio::test] vs block_on), PgPool isolation with
ruby-testing
RSpec testing patterns for Ruby and Rails — factories, mocks, request specs, feature specs, VCR, and SimpleCov coverage.