All skills
jeffallan avatar

/swift-expert

@efebc44
by jeffallanjeffallan/claude-skills12k stars
1,127

Builds iOS/macOS/watchOS/tvOS applications, implements SwiftUI views and state management, designs protocol-oriented architectures, handles async/await concurrency, implements actors for thread safety, and debugs Swift-specific issues. Use when building iOS/macOS applications with Swift 5.9+, SwiftUI, or async/await concurrency. Invoke for protocol-oriented programming, SwiftUI state management, actors, server-side Swift, UIKit integration, Combine, or Vapor.

Use this Skill: https://skilld.dev/gh/jeffallan/claude-skills/swift-expert

This session only. Nothing lands on disk.

referencestesting-patterns.md

≈2.3k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Testing Patterns

XCTest Basics

import XCTest
@testable import MyApp

final class UserTests: XCTestCase {
    var sut: UserManager!

    override func setUp() {
        super.setUp()
        sut = UserManager()
    }

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

    func testUserCreation() {
        // Given
        let name = "John Doe"
        let email = "john@example.com"

        // When
        let user = sut.createUser(name: name, email: email)

        // Then
        XCTAssertEqual(user.name, name)
        XCTAssertEqual(user.email, email)
        XCTAssertNotNil(user.id)
    }

    func testValidation() throws {
        // Unwrapping optionals in tests
        let user = try XCTUnwrap(sut.findUser(id: 123))
        XCTAssertEqual(user.name, "Test User")
    }
}

Async Testing

final class AsyncTests: XCTestCase {
    func testAsyncFunction() async throws {
        // Test async/await code directly
        let result = try await fetchData()
        XCTAssertEqual(result.count, 10)
    }

    func testAsyncSequence() async throws {
        var results: [Int] = []

        for try await value in numberStream() {
            results.append(value)
            if results.count >= 5 {
                break
            }
        }

        XCTAssertEqual(results.count, 5)
    }

    func testWithTimeout() async throws {
        // Test with timeout
        try await withTimeout(seconds: 5) {
            try await longRunningOperation()
        }
    }

    func testConcurrentOperations() async throws {
        async let result1 = fetchData(id: 1)
        async let result2 = fetchData(id: 2)

        let (data1, data2) = try await (result1, result2)

        XCTAssertNotNil(data1)
        XCTAssertNotNil(data2)
    }
}

// Helper for timeout
func withTimeout<T>(
    seconds: TimeInterval,
    operation: @escaping () async throws -> T
) async throws -> T {
    try await withThrowingTaskGroup(of: T.self) { group in
        group.addTask {
            try await operation()
        }

        group.addTask {
            try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
            throw TimeoutError()
        }

        let result = try await group.next()!
        group.cancelAll()
        return result
    }
}

Mocking

// Protocol for dependency injection
protocol DataService {
    func fetch(id: Int) async throws -> Data
    func save(_ data: Data) async throws
}

// Production implementation
class APIDataService: DataService {
    func fetch(id: Int) async throws -> Data {
        // Real API call
    }

    func save(_ data: Data) async throws {
        // Real save operation
    }
}

// Mock for testing
class MockDataService: DataService {
    var fetchCalled = false
    var fetchID: Int?
    var fetchResult: Data?
    var fetchError: Error?

    var saveCalled = false
    var savedData: Data?
    var saveError: Error?

    func fetch(id: Int) async throws -> Data {
        fetchCalled = true
        fetchID = id

        if let error = fetchError {
            throw error
        }

        return fetchResult ?? Data()
    }

    func save(_ data: Data) async throws {
        saveCalled = true
        savedData = data

        if let error = saveError {
            throw error
        }
    }
}

// Using mock in tests
final class DataManagerTests: XCTestCase {
    func testDataFetch() async throws {
        // Given
        let mockService = MockDataService()
        mockService.fetchResult = "test data".data(using: .utf8)
        let manager = DataManager(service: mockService)

        // When
        let result = try await manager.loadData(id: 123)

        // Then
        XCTAssertTrue(mockService.fetchCalled)
        XCTAssertEqual(mockService.fetchID, 123)
        XCTAssertNotNil(result)
    }
}

Test Doubles

// Spy - records interactions
class SpyDelegate: UserManagerDelegate {
    private(set) var didUpdateUserCalled = false
    private(set) var updatedUser: User?
    private(set) var callCount = 0

    func didUpdateUser(_ user: User) {
        didUpdateUserCalled = true
        updatedUser = user
        callCount += 1
    }
}

// Stub - provides predetermined responses
class StubNetworkService: NetworkService {
    var stubbedResponse: Result<Data, Error> = .success(Data())

    func fetch(url: URL) async throws -> Data {
        try stubbedResponse.get()
    }
}

// Fake - working implementation with shortcuts
class FakeDatabase: Database {
    private var storage: [String: Data] = [:]

    func save(key: String, value: Data) {
        storage[key] = value
    }

    func load(key: String) -> Data? {
        storage[key]
    }

    func clear() {
        storage.removeAll()
    }
}

Performance Testing

final class PerformanceTests: XCTestCase {
    func testSortingPerformance() {
        let numbers = (0..<10000).shuffled()

        measure {
            _ = numbers.sorted()
        }
    }

    func testCustomMetrics() {
        let metrics: [XCTMetric] = [
            XCTClockMetric(),
            XCTCPUMetric(),
            XCTMemoryMetric(),
            XCTStorageMetric()
        ]

        let options = XCTMeasureOptions()
        options.iterationCount = 10

        measure(metrics: metrics, options: options) {
            performExpensiveOperation()
        }
    }
}

UI Testing

final class AppUITests: XCTestCase {
    var app: XCUIApplication!

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

    func testLoginFlow() {
        // Test UI interactions
        let emailField = app.textFields["Email"]
        emailField.tap()
        emailField.typeText("test@example.com")

        let passwordField = app.secureTextFields["Password"]
        passwordField.tap()
        passwordField.typeText("password123")

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

        // Verify navigation
        XCTAssertTrue(app.navigationBars["Dashboard"].exists)
    }

    func testButtonEnabled() {
        let button = app.buttons["Submit"]
        XCTAssertFalse(button.isEnabled)

        app.textFields["Username"].tap()
        app.textFields["Username"].typeText("testuser")

        XCTAssertTrue(button.isEnabled)
    }
}

Testing Actors

final class ActorTests: XCTestCase {
    func testActorIsolation() async throws {
        actor Counter {
            private var value = 0

            func increment() -> Int {
                value += 1
                return value
            }

            func reset() {
                value = 0
            }
        }

        let counter = Counter()

        // Test concurrent access
        await withTaskGroup(of: Int.self) { group in
            for _ in 0..<100 {
                group.addTask {
                    await counter.increment()
                }
            }
        }

        let finalValue = await counter.increment()
        XCTAssertEqual(finalValue, 101)
    }
}

Snapshot Testing

import SnapshotTesting

final class ViewSnapshotTests: XCTestCase {
    func testButtonAppearance() {
        let button = UIButton()
        button.setTitle("Tap Me", for: .normal)
        button.backgroundColor = .blue
        button.frame = CGRect(x: 0, y: 0, width: 200, height: 50)

        assertSnapshot(matching: button, as: .image)
    }

    func testViewControllerLayout() {
        let vc = MyViewController()
        assertSnapshot(matching: vc, as: .image(on: .iPhone13))
    }

    func testDarkMode() {
        let view = MyView()
        assertSnapshot(matching: view, as: .image(traits: .init(userInterfaceStyle: .dark)))
    }
}

Test Organization

// MARK: - Test Cases
extension UserManagerTests {
    // MARK: Creation Tests
    func testUserCreation() { }
    func testUserCreationWithInvalidData() { }

    // MARK: Validation Tests
    func testEmailValidation() { }
    func testPasswordValidation() { }

    // MARK: Persistence Tests
    func testUserSave() { }
    func testUserLoad() { }
}

// MARK: - Test Helpers
extension UserManagerTests {
    func makeTestUser() -> User {
        User(name: "Test", email: "test@example.com")
    }

    func setupMockData() {
        // Common test setup
    }
}

Best Practices

  • Use @testable import to test internal types
  • One assertion concept per test (can have multiple XCTAssert calls)
  • Use Given-When-Then pattern for clarity
  • Name tests descriptively: test_methodName_condition_expectedResult
  • Use setUp/tearDown for common test setup
  • Prefer dependency injection for testability
  • Use protocols to enable mocking
  • Test edge cases and error conditions
  • Use async/await for testing async code
  • Measure performance with XCTest metrics
  • Use UI testing for critical user flows
  • Mock external dependencies
  • Keep tests fast and independent
  • Use test doubles appropriately (mock, stub, spy, fake)

Source: SKILL.md on GitHub

1 alert16d5 checks · Risk CRITICAL
  • Gen Agent Trust Hub16d

    The skill provides comprehensive instructions and reference material for professional Swift development on Apple platforms. No malicious patterns, obfuscation, or security risks were identified within the skill instructions, reference files, or code examples.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer6mo

    6 files scanned · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at efebc44. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Steadyupdated 5 months ago
Other metadata
metadata
{
  "author": "https://github.com/Jeffallan",
  "version": "1.1.0",
  "domain": "language",
  "triggers": "Swift, SwiftUI, iOS development, macOS development, async/await Swift, Combine, UIKit, Vapor",
  "role": "specialist",
  "scope": "implementation",
  "output-format": "code",
  "related-skills": null
}

README badge

README badge for jeffallan/claude-skills/swift-expert