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-generatortemplates.md

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

Test Generator Code Templates

Production-ready Swift templates for unit tests, integration tests, and UI tests. Templates cover Swift Testing (iOS 16+) and XCTest frameworks.

Swift Testing Suite Template

Full test suite using Swift Testing with @Suite, @Test, #expect, parameterized tests, and tags.

import Testing
@testable import YourApp

// MARK: - Custom Tags

extension Tag {
    @Tag static var viewModel: Self
    @Tag static var repository: Self
    @Tag static var networking: Self
    @Tag static var validation: Self
}

// MARK: - Item ViewModel Tests

@Suite("ItemViewModel", .tags(.viewModel))
struct ItemViewModelTests {

    // MARK: - Properties

    let mockRepository: MockItemRepository
    let sut: ItemViewModel

    // MARK: - Initialization (runs before each test)

    init() {
        mockRepository = MockItemRepository()
        sut = ItemViewModel(repository: mockRepository)
    }

    // MARK: - Loading

    @Test("loads items successfully")
    func loadsItemsSuccessfully() async throws {
        mockRepository.stubbedItems = [
            Item(id: "1", title: "First"),
            Item(id: "2", title: "Second"),
            Item(id: "3", title: "Third")
        ]

        await sut.loadItems()

        #expect(sut.items.count == 3)
        #expect(sut.isLoading == false)
        #expect(sut.errorMessage == nil)
    }

    @Test("shows empty state when no items exist")
    func showsEmptyState() async {
        mockRepository.stubbedItems = []

        await sut.loadItems()

        #expect(sut.items.isEmpty)
        #expect(sut.showEmptyState == true)
    }

    @Test("sets loading state during fetch")
    func setsLoadingState() async {
        mockRepository.delay = .milliseconds(100)

        let loadTask = Task { await sut.loadItems() }

        // Allow the task to start
        try? await Task.sleep(for: .milliseconds(10))
        #expect(sut.isLoading == true)

        await loadTask.value
        #expect(sut.isLoading == false)
    }

    // MARK: - Error Handling

    @Test("handles repository failure")
    func handlesRepositoryFailure() async {
        mockRepository.shouldFail = true
        mockRepository.stubbedError = TestError.networkUnavailable

        await sut.loadItems()

        #expect(sut.state == .error)
        #expect(sut.errorMessage != nil)
        #expect(sut.items.isEmpty)
    }

    // MARK: - Parameterized Tests

    @Test("validates email format", arguments: [
        ("user@example.com", true),
        ("user@domain.co.uk", true),
        ("invalid-email", false),
        ("no@tld", false),
        ("@missing-local.com", false),
        ("user@.com", false)
    ])
    func validatesEmailFormat(email: String, isValid: Bool) {
        #expect(EmailValidator.isValid(email) == isValid)
    }

    @Test("filters items by category", arguments: [
        ItemCategory.active,
        ItemCategory.archived,
        ItemCategory.draft
    ])
    func filtersItemsByCategory(category: ItemCategory) async {
        mockRepository.stubbedItems = Item.sampleItems

        await sut.filterItems(by: category)

        let allMatchCategory = sut.items.allSatisfy { $0.category == category }
        #expect(allMatchCategory, "All displayed items should match the selected category")
    }

    // MARK: - Deletion

    @Test("deletes item and refreshes list")
    func deletesItem() async throws {
        let item = Item(id: "1", title: "To Delete")
        mockRepository.stubbedItems = [item]
        await sut.loadItems()

        await sut.deleteItem(item)

        #expect(mockRepository.deleteCallCount == 1)
        #expect(mockRepository.lastDeletedItemID == "1")
    }

    // MARK: - Confirmation (require for optionals)

    @Test("unwraps selected item safely")
    func unwrapsSelectedItem() async throws {
        mockRepository.stubbedItems = [Item(id: "1", title: "First")]
        await sut.loadItems()

        sut.selectItem(at: 0)

        let selected = try #require(sut.selectedItem, "Expected an item to be selected")
        #expect(selected.id == "1")
    }
}

XCTest Class Template

Traditional XCTest class with setUp/tearDown, async tests, and expectations.

import XCTest
@testable import YourApp

final class ItemViewModelXCTests: XCTestCase {

    // MARK: - Properties

    private var sut: ItemViewModel!
    private var mockRepository: MockItemRepository!

    // MARK: - Lifecycle

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

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

    // MARK: - Loading Tests

    func testLoadsItemsSuccessfully() async throws {
        mockRepository.stubbedItems = [
            Item(id: "1", title: "First"),
            Item(id: "2", title: "Second")
        ]

        await sut.loadItems()

        XCTAssertEqual(sut.items.count, 2)
        XCTAssertFalse(sut.isLoading)
        XCTAssertNil(sut.errorMessage)
    }

    func testShowsEmptyState() async {
        mockRepository.stubbedItems = []

        await sut.loadItems()

        XCTAssertTrue(sut.items.isEmpty)
        XCTAssertTrue(sut.showEmptyState)
    }

    // MARK: - Error Handling Tests

    func testHandlesRepositoryFailure() async {
        mockRepository.shouldFail = true

        await sut.loadItems()

        XCTAssertEqual(sut.state, .error)
        XCTAssertNotNil(sut.errorMessage)
        XCTAssertTrue(sut.items.isEmpty)
    }

    // MARK: - Deletion Tests

    func testDeletesItemAndRefreshes() async throws {
        let item = Item(id: "1", title: "To Delete")
        mockRepository.stubbedItems = [item]
        await sut.loadItems()

        await sut.deleteItem(item)

        XCTAssertEqual(mockRepository.deleteCallCount, 1)
        XCTAssertEqual(mockRepository.lastDeletedItemID, "1")
    }

    // MARK: - Unwrapping Tests

    func testSelectedItemUnwrap() async throws {
        mockRepository.stubbedItems = [Item(id: "1", title: "First")]
        await sut.loadItems()

        sut.selectItem(at: 0)

        let selected = try XCTUnwrap(sut.selectedItem, "Expected an item to be selected")
        XCTAssertEqual(selected.id, "1")
    }

    // MARK: - Async Expectation Tests

    func testNotificationPostedOnSave() async {
        let expectation = expectation(forNotification: .itemSaved, object: nil)

        await sut.saveItem(Item(id: "1", title: "New"))

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

Mock Object Template

Protocol-based mock with call counting, argument capture, stubbed returns, and failure injection.

import Foundation
@testable import YourApp

// MARK: - Protocol

/// Protocol defining repository operations.
/// Both the real implementation and mock conform to this.
protocol ItemRepositoryProtocol: Sendable {
    func fetchItems() async throws -> [Item]
    func fetchItem(id: String) async throws -> Item?
    func saveItem(_ item: Item) async throws
    func deleteItem(id: String) async throws
}

// MARK: - Mock Implementation

/// A mock repository for testing.
///
/// Features:
/// - Stubbed return values for all methods
/// - Call counting for verification
/// - Argument capture for assertion
/// - Configurable failure injection
/// - Optional artificial delay for testing loading states
final class MockItemRepository: ItemRepositoryProtocol, @unchecked Sendable {

    // MARK: - Stubbed Values

    /// Items returned by `fetchItems()`.
    var stubbedItems: [Item] = []

    /// Item returned by `fetchItem(id:)`. Defaults to looking up `stubbedItems` by ID.
    var stubbedItemByID: [String: Item] = [:]

    /// Error thrown when `shouldFail` is `true`.
    var stubbedError: Error = TestError.mockFailure

    // MARK: - Configuration

    /// When `true`, all methods throw `stubbedError`.
    var shouldFail = false

    /// Optional delay before returning results (for testing loading states).
    var delay: Duration?

    // MARK: - Call Tracking: fetchItems

    private(set) var fetchItemsCallCount = 0

    func fetchItems() async throws -> [Item] {
        fetchItemsCallCount += 1
        if let delay { try? await Task.sleep(for: delay) }
        if shouldFail { throw stubbedError }
        return stubbedItems
    }

    // MARK: - Call Tracking: fetchItem

    private(set) var fetchItemCallCount = 0
    private(set) var lastFetchedItemID: String?

    func fetchItem(id: String) async throws -> Item? {
        fetchItemCallCount += 1
        lastFetchedItemID = id
        if shouldFail { throw stubbedError }
        return stubbedItemByID[id] ?? stubbedItems.first { $0.id == id }
    }

    // MARK: - Call Tracking: saveItem

    private(set) var saveCallCount = 0
    private(set) var savedItems: [Item] = []

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

    // MARK: - Call Tracking: deleteItem

    private(set) var deleteCallCount = 0
    private(set) var lastDeletedItemID: String?

    func deleteItem(id: String) async throws {
        deleteCallCount += 1
        lastDeletedItemID = id
        if shouldFail { throw stubbedError }
        stubbedItems.removeAll { $0.id == id }
    }

    // MARK: - Reset

    /// Reset all call counts and captured arguments. Useful between tests if sharing a mock.
    func reset() {
        fetchItemsCallCount = 0
        fetchItemCallCount = 0
        lastFetchedItemID = nil
        saveCallCount = 0
        savedItems = []
        deleteCallCount = 0
        lastDeletedItemID = nil
        shouldFail = false
    }
}

// MARK: - Test Errors

/// Common errors used in test mocks.
enum TestError: LocalizedError, Equatable {
    case mockFailure
    case networkUnavailable
    case unauthorized
    case notFound
    case timeout

    var errorDescription: String? {
        switch self {
        case .mockFailure: return "Mock failure for testing"
        case .networkUnavailable: return "Network is unavailable"
        case .unauthorized: return "Unauthorized access"
        case .notFound: return "Resource not found"
        case .timeout: return "Request timed out"
        }
    }
}

UI Test Screen Object Template

Screen Object pattern for XCUITest with page object encapsulation, assertions, and flow navigation.

import XCTest

// MARK: - Base Screen

/// Base class for all screen objects, providing common utilities.
class BaseScreen {
    let app: XCUIApplication

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

    /// Wait for an element to exist within a timeout.
    @discardableResult
    func waitForElement(
        _ element: XCUIElement,
        timeout: TimeInterval = 5
    ) -> Bool {
        element.waitForExistence(timeout: timeout)
    }
}

// MARK: - Home Screen

final class HomeScreen: BaseScreen {

    // MARK: - Elements

    var navigationTitle: XCUIElement {
        app.navigationBars["Items"]
    }

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

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

    var emptyStateLabel: XCUIElement {
        app.staticTexts["No items yet"]
    }

    var searchField: XCUIElement {
        app.searchFields.firstMatch
    }

    func itemCell(at index: Int) -> XCUIElement {
        itemList.cells.element(boundBy: index)
    }

    func itemCell(titled title: String) -> XCUIElement {
        itemList.cells.staticTexts[title]
    }

    // MARK: - Actions

    @discardableResult
    func tapItem(at index: Int) -> DetailScreen {
        itemCell(at: index).tap()
        return DetailScreen(app: app)
    }

    @discardableResult
    func tapItem(titled title: String) -> DetailScreen {
        itemCell(titled: title).tap()
        return DetailScreen(app: app)
    }

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

    func search(for query: String) {
        searchField.tap()
        searchField.typeText(query)
    }

    func deleteItem(at index: Int) {
        let cell = itemCell(at: index)
        cell.swipeLeft()
        app.buttons["Delete"].tap()
    }

    // MARK: - Assertions

    func assertItemCount(_ count: Int, file: StaticString = #file, line: UInt = #line) {
        XCTAssertEqual(itemList.cells.count, count, "Expected \(count) items", file: file, line: line)
    }

    func assertEmptyStateVisible(file: StaticString = #file, line: UInt = #line) {
        XCTAssertTrue(emptyStateLabel.exists, "Empty state should be visible", file: file, line: line)
    }

    func assertItemExists(titled title: String, file: StaticString = #file, line: UInt = #line) {
        let cell = itemCell(titled: title)
        XCTAssertTrue(cell.waitForExistence(timeout: 3), "Item '\(title)' should exist", file: file, line: line)
    }
}

// MARK: - Detail Screen

final class DetailScreen: BaseScreen {

    var titleLabel: XCUIElement {
        app.staticTexts["itemTitle"]
    }

    var editButton: XCUIElement {
        app.buttons["Edit"]
    }

    var deleteButton: XCUIElement {
        app.buttons["Delete"]
    }

    var backButton: XCUIElement {
        app.navigationBars.buttons.firstMatch
    }

    @discardableResult
    func tapEdit() -> EditScreen {
        editButton.tap()
        return EditScreen(app: app)
    }

    @discardableResult
    func tapBack() -> HomeScreen {
        backButton.tap()
        return HomeScreen(app: app)
    }

    func assertTitle(_ title: String, file: StaticString = #file, line: UInt = #line) {
        XCTAssertEqual(titleLabel.label, title, file: file, line: line)
    }
}

// MARK: - Edit Screen

final class EditScreen: BaseScreen {

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

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

    var cancelButton: XCUIElement {
        app.buttons["Cancel"]
    }

    func enterTitle(_ title: String) {
        titleField.tap()
        titleField.clearAndTypeText(title)
    }

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

    @discardableResult
    func tapCancel() -> HomeScreen {
        cancelButton.tap()
        return HomeScreen(app: app)
    }
}

// MARK: - UI Test Case

final class ItemFlowUITests: XCTestCase {

    private var app: XCUIApplication!

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

        app = XCUIApplication()
        app.launchArguments = ["--uitesting"]
        app.launchEnvironment = ["DISABLE_ANIMATIONS": "1"]
        app.launch()
    }

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

    func testCreateNewItem() {
        let home = HomeScreen(app: app)

        let edit = home.tapAddButton()
        edit.enterTitle("New Item")
        let updatedHome = edit.tapSave()

        updatedHome.assertItemExists(titled: "New Item")
    }

    func testDeleteItem() {
        let home = HomeScreen(app: app)
        home.assertItemCount(3)

        home.deleteItem(at: 0)

        home.assertItemCount(2)
    }

    func testNavigateToDetailAndBack() {
        let home = HomeScreen(app: app)

        let detail = home.tapItem(at: 0)
        detail.assertTitle("First Item")

        let backHome = detail.tapBack()
        backHome.assertItemCount(3)
    }
}

// MARK: - XCUIElement Helpers

extension XCUIElement {
    /// Clear existing text and type new text.
    func clearAndTypeText(_ text: String) {
        guard let currentValue = value as? String, !currentValue.isEmpty else {
            typeText(text)
            return
        }
        let deleteString = String(repeating: XCUIKeyboardKey.delete.rawValue, count: currentValue.count)
        typeText(deleteString)
        typeText(text)
    }
}

Test Helpers Template

Factory methods, test fixtures, and convenience extensions for building test data.

import Foundation
@testable import YourApp

// MARK: - Test Fixtures

/// Factory methods for creating test data.
/// Use these instead of constructing models directly in each test.
enum TestFixtures {

    // MARK: - Items

    /// A single default item for simple tests.
    static let defaultItem = Item(
        id: "test-1",
        title: "Test Item",
        description: "A test item for unit tests",
        category: .active,
        createdAt: Date(timeIntervalSince1970: 1_700_000_000)
    )

    /// A collection of sample items covering various states.
    static let sampleItems: [Item] = [
        Item(id: "1", title: "Active Item", category: .active, createdAt: .now),
        Item(id: "2", title: "Archived Item", category: .archived, createdAt: .now),
        Item(id: "3", title: "Draft Item", category: .draft, createdAt: .now)
    ]

    /// An empty array for testing empty states.
    static let noItems: [Item] = []

    // MARK: - Users

    static let defaultUser = User(
        id: "user-1",
        name: "Test User",
        email: "test@example.com"
    )

    // MARK: - Builders

    /// Create an item with customized properties.
    static func item(
        id: String = UUID().uuidString,
        title: String = "Test Item",
        description: String = "Description",
        category: ItemCategory = .active,
        createdAt: Date = .now
    ) -> Item {
        Item(
            id: id,
            title: title,
            description: description,
            category: category,
            createdAt: createdAt
        )
    }

    /// Create a batch of items for list tests.
    static func items(count: Int, category: ItemCategory = .active) -> [Item] {
        (0..<count).map { index in
            item(
                id: "item-\(index)",
                title: "Item \(index)",
                category: category
            )
        }
    }
}

// MARK: - Model Extensions for Testing

extension Item {
    /// A collection of sample items covering all categories.
    static let sampleItems = TestFixtures.sampleItems
}

// MARK: - Async Test Helpers

/// Utilities for testing asynchronous code.
enum AsyncTestHelpers {

    /// Run an async block with a timeout. Throws if the block does not complete in time.
    static func withTimeout<T>(
        seconds: TimeInterval = 5,
        operation: @escaping @Sendable () 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(for: .seconds(seconds))
                throw TestError.timeout
            }
            let result = try await group.next()!
            group.cancelAll()
            return result
        }
    }
}

// MARK: - Date Helpers

extension Date {
    /// Create a date relative to now for test assertions.
    static func hoursFromNow(_ hours: Double) -> Date {
        Date().addingTimeInterval(hours * 3600)
    }

    /// Create a date in the past for test data.
    static func daysAgo(_ days: Int) -> Date {
        Calendar.current.date(byAdding: .day, value: -days, to: Date())!
    }
}

// MARK: - JSON Test Helpers

/// Helpers for testing Codable conformance.
enum CodableTestHelpers {

    /// Encode and decode a value, verifying round-trip fidelity.
    static func verifyRoundTrip<T: Codable & Equatable>(
        _ value: T,
        file: StaticString = #file,
        line: UInt = #line
    ) throws -> T {
        let encoder = JSONEncoder()
        encoder.dateEncodingStrategy = .iso8601

        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .iso8601

        let data = try encoder.encode(value)
        let decoded = try decoder.decode(T.self, from: data)
        return decoded
    }

    /// Decode a value from a JSON string.
    static func decode<T: Decodable>(_ type: T.Type, from json: String) throws -> T {
        let data = Data(json.utf8)
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .iso8601
        return try decoder.decode(type, from: data)
    }
}

Patterns: Good and Bad

Test Isolation

// ✅ Good: Each test creates its own dependencies (Swift Testing)
@Suite("Tests")
struct MyTests {
    let sut: ItemViewModel
    let mock: MockItemRepository

    init() {
        mock = MockItemRepository()
        sut = ItemViewModel(repository: mock)
    }
}

// ❌ Bad: Shared mutable state without reset
@Suite("Tests")
struct MyTests {
    static let mock = MockItemRepository()  // Shared across all tests!
    static let sut = ItemViewModel(repository: mock)
}

Assertions

// ✅ Good: Specific assertions with context
#expect(items.count == 3, "Expected 3 items after loading")
#expect(viewModel.state == .loaded)
let first = try #require(items.first)
#expect(first.title == "Expected Title")

// ❌ Bad: Vague assertions
#expect(!items.isEmpty)           // How many should there be?
#expect(viewModel.state != .idle) // What state SHOULD it be?

Error Testing

// ✅ Good: Test specific error type (Swift Testing)
@Test("throws notFound for missing item")
func throwsNotFound() async {
    let repo = MockItemRepository()
    repo.shouldFail = true
    repo.stubbedError = TestError.notFound

    await #expect(throws: TestError.notFound) {
        try await repo.fetchItem(id: "missing")
    }
}

// ❌ Bad: Only test that "some" error is thrown
@Test("throws error")
func throwsError() async {
    await #expect(throws: (any Error).self) {
        try await repo.fetchItem(id: "missing")
    }
}

Async Tests

// ✅ Good: Use async/await directly (Swift Testing)
@Test("fetches items asynchronously")
func fetchesItems() async throws {
    let items = try await service.fetchItems()
    #expect(items.count > 0)
}

// ❌ Bad: Using XCTest expectations for simple async (when you could use async/await)
func testFetchesItems() {
    let exp = expectation(description: "fetch")
    Task {
        let items = try await service.fetchItems()
        XCTAssertGreaterThan(items.count, 0)
        exp.fulfill()
    }
    wait(for: [exp], timeout: 5)
}

Optional Handling in Tests

// ✅ Good: Use #require to unwrap (Swift Testing)
@Test("selected item has correct title")
func selectedItemTitle() async throws {
    await sut.loadItems()
    sut.selectItem(at: 0)

    let selected = try #require(sut.selectedItem)
    #expect(selected.title == "First")
}

// ✅ Good: Use XCTUnwrap (XCTest)
func testSelectedItemTitle() async throws {
    await sut.loadItems()
    sut.selectItem(at: 0)

    let selected = try XCTUnwrap(sut.selectedItem)
    XCTAssertEqual(selected.title, "First")
}

// ❌ Bad: Force unwrap in tests
func testSelectedItemTitle() async {
    await sut.loadItems()
    sut.selectItem(at: 0)

    XCTAssertEqual(sut.selectedItem!.title, "First")  // Crash if nil
}

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