All skills

Code generator skills that produce production-ready Swift code for common app components. Use when user wants to add logging, analytics, onboarding, review prompts, networking, authentication, paywalls, settings, persistence, error monitoring, CI/CD pipelines, localization, push notifications, deep linking, testing, accessibility, widgets, feature flags, app icons, image caching, pagination, HTTP caching, share cards, social export, subscription lifecycle, referral systems, watermarks, streak tracking, milestone celebrations, what's new screens, lapsed user re-engagement, usage insights, variable rewards, consent flows, account deletion, permission priming, force updates, state restoration, debug menus, offline queues, feedback forms, announcement banners, quick win sessions, Spotlight indexing, App Clips, screenshot automation, background processing, app extensions, data export, or SwiftUI preview sample data and variant matrices.

Use this Skill: https://skilld.dev/gh/rshankras/claude-code-apple-skills/generators

This session only. Nothing lands on disk.

test-generatortesting-patterns.md

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

Testing Patterns

Best practices for testing iOS/macOS apps with Swift Testing and XCTest.

Swift Testing Framework (iOS 16+)

Test Suite Structure

import Testing
@testable import YourApp

/// Group related tests with @Suite
@Suite("User Authentication Tests")
struct AuthenticationTests {

    // Shared setup for all tests in suite
    let authService: AuthService

    init() {
        authService = AuthService(client: MockAPIClient())
    }

    @Test("successful login with valid credentials")
    func successfulLogin() async throws {
        let result = try await authService.login(
            email: "test@example.com",
            password: "password123"
        )

        #expect(result.isAuthenticated)
        #expect(result.user.email == "test@example.com")
    }

    @Test("fails with invalid credentials")
    func failsWithInvalidCredentials() async {
        await #expect(throws: AuthError.invalidCredentials) {
            try await authService.login(
                email: "test@example.com",
                password: "wrong"
            )
        }
    }
}

Parameterized Tests

@Suite("Validation Tests")
struct ValidationTests {

    @Test("email validation", arguments: [
        ("valid@email.com", true),
        ("user@domain.co.uk", true),
        ("name+tag@example.org", true),
        ("invalid", false),
        ("@nodomain.com", false),
        ("spaces in@email.com", false),
        ("", false)
    ])
    func emailValidation(email: String, expectedValid: Bool) {
        let result = Validator.isValidEmail(email)
        #expect(result == expectedValid, "Email '\(email)' validation failed")
    }

    @Test("password strength", arguments: [
        ("weak", PasswordStrength.weak),
        ("Medium1", .medium),
        ("Strong1!", .strong),
        ("VeryStr0ng!Pass", .strong)
    ])
    func passwordStrength(password: String, expected: PasswordStrength) {
        #expect(Validator.passwordStrength(password) == expected)
    }
}

Test Traits

@Suite("Feature Tests")
struct FeatureTests {

    @Test("premium feature access", .tags(.premium))
    func premiumFeatureAccess() async throws {
        // Test premium feature
    }

    @Test("slow integration test", .timeLimit(.minutes(5)))
    func slowIntegrationTest() async throws {
        // Long-running test
    }

    @Test("disabled pending fix", .disabled("Waiting for API fix"))
    func disabledTest() {
        // Won't run
    }

    @Test("iOS only", .enabled(if: ProcessInfo.processInfo.isMacCatalystApp == false))
    func iOSOnlyTest() {
        // Only runs on iOS
    }
}

extension Tag {
    @Tag static var premium: Self
    @Tag static var integration: Self
    @Tag static var slow: Self
}

Expectations

@Suite("Expectations")
struct ExpectationTests {

    @Test("basic expectations")
    func basicExpectations() {
        let value = 42

        #expect(value == 42)
        #expect(value > 0)
        #expect(value != 0)
    }

    @Test("optional expectations")
    func optionalExpectations() throws {
        let optional: String? = "hello"

        // Unwrap and continue
        let unwrapped = try #require(optional)
        #expect(unwrapped == "hello")

        // Check nil
        let nilValue: String? = nil
        #expect(nilValue == nil)
    }

    @Test("collection expectations")
    func collectionExpectations() {
        let items = ["a", "b", "c"]

        #expect(items.count == 3)
        #expect(items.contains("b"))
        #expect(!items.isEmpty)
    }

    @Test("throwing expectations")
    func throwingExpectations() async {
        await #expect(throws: NetworkError.self) {
            try await failingOperation()
        }

        // Specific error
        await #expect(throws: NetworkError.timeout) {
            try await timeoutOperation()
        }
    }
}

XCTest Framework

Test Case Structure

import XCTest
@testable import YourApp

final class ItemViewModelTests: XCTestCase {

    // MARK: - Properties

    private var sut: ItemViewModel!  // System Under Test
    private var mockRepository: MockItemRepository!

    // MARK: - Setup & Teardown

    override func setUp() {
        super.setUp()
        mockRepository = MockItemRepository()
        sut = ItemViewModel(repository: mockRepository)
    }

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

    // MARK: - Tests

    func test_loadItems_whenSuccessful_updatesItems() async throws {
        // Arrange
        mockRepository.items = [Item(id: "1", name: "Test")]

        // Act
        await sut.loadItems()

        // Assert
        XCTAssertEqual(sut.items.count, 1)
        XCTAssertEqual(sut.items.first?.name, "Test")
    }

    func test_loadItems_whenFails_showsError() async {
        // Arrange
        mockRepository.shouldFail = true

        // Act
        await sut.loadItems()

        // Assert
        XCTAssertTrue(sut.showError)
        XCTAssertNotNil(sut.errorMessage)
    }
}

Async Testing

final class AsyncTests: XCTestCase {

    func test_asyncOperation_completesSuccessfully() async throws {
        let service = DataService()

        let result = try await service.fetchData()

        XCTAssertFalse(result.isEmpty)
    }

    func test_asyncOperation_withTimeout() async throws {
        let expectation = expectation(description: "Data loaded")

        Task {
            _ = try await service.fetchData()
            expectation.fulfill()
        }

        await fulfillment(of: [expectation], timeout: 5.0)
    }
}

Mock Objects

Protocol-Based Mocking

// Protocol
protocol ItemRepository: Sendable {
    func fetchItems() async throws -> [Item]
    func saveItem(_ item: Item) async throws
    func deleteItem(id: String) async throws
}

// Mock Implementation
final class MockItemRepository: ItemRepository, @unchecked Sendable {

    // Configurable state
    var items: [Item] = []
    var shouldFail = false
    var error: Error = MockError.general

    // Call tracking
    var fetchCallCount = 0
    var saveCallCount = 0
    var deleteCallCount = 0
    var lastSavedItem: Item?
    var lastDeletedId: String?

    func fetchItems() async throws -> [Item] {
        fetchCallCount += 1
        if shouldFail { throw error }
        return items
    }

    func saveItem(_ item: Item) async throws {
        saveCallCount += 1
        lastSavedItem = item
        if shouldFail { throw error }
        items.append(item)
    }

    func deleteItem(id: String) async throws {
        deleteCallCount += 1
        lastDeletedId = id
        if shouldFail { throw error }
        items.removeAll { $0.id == id }
    }

    func reset() {
        items = []
        shouldFail = false
        fetchCallCount = 0
        saveCallCount = 0
        deleteCallCount = 0
        lastSavedItem = nil
        lastDeletedId = nil
    }
}

enum MockError: Error {
    case general
    case network
    case notFound
}

Spy Pattern

final class SpyAnalytics: AnalyticsService {
    var trackedEvents: [(name: String, properties: [String: Any])] = []

    func track(event: String, properties: [String: Any]) {
        trackedEvents.append((event, properties))
    }

    func hasTracked(event: String) -> Bool {
        trackedEvents.contains { $0.name == event }
    }

    func eventCount(for event: String) -> Int {
        trackedEvents.filter { $0.name == event }.count
    }
}

ViewModel Testing

Testing State Changes

@Suite("ItemViewModel State Tests")
struct ItemViewModelStateTests {

    @Test("initial state is idle")
    func initialState() {
        let vm = ItemViewModel(repository: MockItemRepository())
        #expect(vm.state == .idle)
    }

    @Test("loading state during fetch")
    func loadingState() async {
        let slowRepo = SlowMockRepository(delay: 0.1)
        let vm = ItemViewModel(repository: slowRepo)

        async let loadTask = vm.loadItems()

        // Check loading state
        try? await Task.sleep(for: .milliseconds(50))
        #expect(vm.state == .loading)

        await loadTask
        #expect(vm.state == .loaded)
    }

    @Test("error state on failure")
    func errorState() async {
        let failingRepo = MockItemRepository(shouldFail: true)
        let vm = ItemViewModel(repository: failingRepo)

        await vm.loadItems()

        #expect(vm.state == .error)
    }
}

Testing User Actions

@Suite("User Action Tests")
struct UserActionTests {

    @Test("adding item updates list")
    func addingItem() async throws {
        let repo = MockItemRepository()
        let vm = ItemViewModel(repository: repo)

        await vm.addItem(title: "New Item")

        #expect(vm.items.count == 1)
        #expect(vm.items.first?.title == "New Item")
        #expect(repo.saveCallCount == 1)
    }

    @Test("deleting item removes from list")
    func deletingItem() async throws {
        let repo = MockItemRepository(items: [
            Item(id: "1", title: "Item 1"),
            Item(id: "2", title: "Item 2")
        ])
        let vm = ItemViewModel(repository: repo)
        await vm.loadItems()

        await vm.deleteItem(id: "1")

        #expect(vm.items.count == 1)
        #expect(vm.items.first?.id == "2")
    }
}

UI Testing

Page Object Pattern

import XCTest

// Base screen
class BaseScreen {
    let app: XCUIApplication

    init(app: XCUIApplication) {
        self.app = app
    }

    func waitForElement(_ element: XCUIElement, timeout: TimeInterval = 5) -> Bool {
        element.waitForExistence(timeout: timeout)
    }
}

// Home screen
class HomeScreen: BaseScreen {

    var itemList: XCUIElement {
        app.collectionViews["itemList"]
    }

    var addButton: XCUIElement {
        app.buttons["addButton"]
    }

    var itemCells: XCUIElementQuery {
        itemList.cells
    }

    @discardableResult
    func tapAddButton() -> AddItemScreen {
        addButton.tap()
        return AddItemScreen(app: app)
    }

    func tapItem(at index: Int) -> ItemDetailScreen {
        itemCells.element(boundBy: index).tap()
        return ItemDetailScreen(app: app)
    }

    func itemCount() -> Int {
        itemCells.count
    }
}

// Add item screen
class AddItemScreen: BaseScreen {

    var titleField: XCUIElement {
        app.textFields["titleField"]
    }

    var saveButton: XCUIElement {
        app.buttons["saveButton"]
    }

    func enterTitle(_ title: String) -> Self {
        titleField.tap()
        titleField.typeText(title)
        return self
    }

    @discardableResult
    func tapSave() -> HomeScreen {
        saveButton.tap()
        return HomeScreen(app: app)
    }
}

UI Test Cases

import XCTest

final class ItemFlowUITests: XCTestCase {

    var app: XCUIApplication!
    var homeScreen: HomeScreen!

    override func setUp() {
        super.setUp()
        continueAfterFailure = false

        app = XCUIApplication()
        app.launchArguments = ["--uitesting"]
        app.launch()

        homeScreen = HomeScreen(app: app)
    }

    func test_addItem_flow() {
        // Initial state
        let initialCount = homeScreen.itemCount()

        // Add new item
        homeScreen
            .tapAddButton()
            .enterTitle("Test Item")
            .tapSave()

        // Verify
        XCTAssertEqual(homeScreen.itemCount(), initialCount + 1)
    }

    func test_viewItemDetail_flow() {
        // Tap first item
        let detailScreen = homeScreen.tapItem(at: 0)

        // Verify detail screen appeared
        XCTAssertTrue(detailScreen.titleLabel.exists)
    }
}

Test Utilities

Test Data Builders

struct ItemBuilder {
    private var id = UUID().uuidString
    private var title = "Test Item"
    private var isCompleted = false
    private var createdAt = Date()

    func with(id: String) -> Self {
        var copy = self
        copy.id = id
        return copy
    }

    func with(title: String) -> Self {
        var copy = self
        copy.title = title
        return copy
    }

    func completed() -> Self {
        var copy = self
        copy.isCompleted = true
        return copy
    }

    func build() -> Item {
        Item(
            id: id,
            title: title,
            isCompleted: isCompleted,
            createdAt: createdAt
        )
    }
}

// Usage
let item = ItemBuilder()
    .with(title: "Custom Title")
    .completed()
    .build()

Async Test Helpers

extension XCTestCase {

    func waitForAsync(
        timeout: TimeInterval = 5,
        condition: @escaping () async -> Bool
    ) async throws {
        let deadline = Date().addingTimeInterval(timeout)

        while Date() < deadline {
            if await condition() {
                return
            }
            try await Task.sleep(for: .milliseconds(100))
        }

        XCTFail("Condition not met within \(timeout) seconds")
    }
}

Best Practices

Naming Conventions

// Swift Testing - use descriptive strings
@Test("user can successfully log in with valid credentials")

// XCTest - use method naming pattern
func test_login_withValidCredentials_succeeds()
func test_login_withInvalidPassword_fails()
func test_logout_clearsUserSession()

Test Organization

Tests/
├── UnitTests/
│   ├── ViewModels/
│   ├── Services/
│   ├── Repositories/
│   └── Utilities/
├── IntegrationTests/
│   ├── API/
│   └── Database/
├── UITests/
│   ├── Screens/
│   ├── Flows/
│   └── Helpers/
└── Mocks/
    ├── MockAPIClient.swift
    ├── MockRepository.swift
    └── TestData.swift

Code Coverage

# Generate coverage report
xcodebuild test \
  -scheme YourApp \
  -destination 'platform=iOS Simulator,name=iPhone 16' \
  -enableCodeCoverage YES \
  -resultBundlePath TestResults.xcresult

# View coverage
xcrun xccov view --report TestResults.xcresult

Source: SKILL.md on GitHub

1 alert16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The 'generators' skill provides a robust set of templates and workflows for generating Swift code for iOS and macOS applications. Security analysis identified a surface for indirect prompt injection because the skill reads local project files to adapt its generation logic. It also utilizes dynamic execution by generating and running local Swift scripts to create application icons and uses shell commands to process images and audit project code. All external references are to well-known, established services.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: MEDIUM · 1 issue

  • Runlayer7mo

    59/179 files flagged

  • ZeroLeaks5mo

    Scan incomplete

Signed by skilld at 30e898f. 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 2 months ago
What it can do
Reads files Edits files Runs commands
last_verified
2026-07-16
review_by
2027-06-22
os_version
iOS 27 / macOS 27
All 7 allowed tools
ReadWriteEditGlobGrepBashAskUserQuestion

README badge

README badge for rshankras/claude-code-apple-skills/generators