test-generator

Generate test templates for unit tests, integration tests, and UI tests using Swift Testing and XCTest. Use when adding tests to iOS/macOS apps.

110 stars

Best use case

test-generator is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

Generate test templates for unit tests, integration tests, and UI tests using Swift Testing and XCTest. Use when adding tests to iOS/macOS apps.

Teams using test-generator 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

$curl -o ~/.claude/skills/test-generator/SKILL.md --create-dirs "https://raw.githubusercontent.com/gustavscirulis/snapgrid/main/.claude/skills/skills/generators/test-generator/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/test-generator/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How test-generator Compares

Feature / Agenttest-generatorStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

Generate test templates for unit tests, integration tests, and UI tests using Swift Testing and XCTest. Use when adding tests to iOS/macOS apps.

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.

Related Guides

SKILL.md Source

# Test Generator

Generate test templates for unit tests, integration tests, and UI tests in iOS/macOS apps.

## When This Skill Activates

Use this skill when the user:
- Asks to "add tests" or "write tests" for their app
- Asks about unit testing, UI testing, or XCTest
- Wants to test ViewModels, services, or repositories
- Mentions TDD or test-driven development
- Asks about Swift Testing framework (`@Test`, `#expect`, `@Suite`)
- Wants mock objects or test helpers
- Asks about snapshot testing or preview tests

## Decision Tree

```
What tests do you need?
|
+-- Unit tests for business logic
|   +-- Swift Testing (@Test, #expect) -- recommended for iOS 16+
|   +-- XCTest -- for iOS 13-15 support or existing XCTest projects
|
+-- Integration tests (component interactions)
|   +-- Protocol-based mocks with dependency injection
|
+-- UI tests
|   +-- XCUITest with Screen Object pattern
|
+-- Snapshot/preview tests
    +-- PreviewSnapshots or swift-snapshot-testing
```

## Pre-Generation Checks

### 1. Project Context Detection
- [ ] Identify existing test targets and test runner
- [ ] Detect testing framework already in use (Swift Testing vs XCTest)
- [ ] Verify deployment target (Swift Testing requires iOS 16+ / macOS 13+)
- [ ] Identify project architecture pattern (MVVM, TCA, Repository, etc.)
- [ ] Locate source file directories

### 2. Conflict Detection
Search for existing test infrastructure:
```
Glob: **/*Tests.swift, **/*Tests/**/*.swift, **/*Spec.swift
Grep: "import XCTest" or "import Testing" or "@Suite" or "@Test"
Grep: "MockItemRepository" or "protocol.*Repository" or "class Mock"
```

If existing tests are found:
- Ask user whether to follow the existing framework (XCTest vs Swift Testing) or migrate
- Check for existing mock objects to reuse or extend
- Identify existing test helpers and factories

If a test target already exists:
- Add new tests to the existing target -- do NOT create a new target
- Follow the existing directory structure and naming conventions

### 3. Architecture Detection
```
Grep: "ViewModel" or "Reducer" or "UseCase" or "Repository" or "Service"
Glob: **/*ViewModel.swift, **/*Reducer.swift, **/*Repository.swift
```

This determines which test templates to generate (ViewModel tests, Reducer tests, etc.).

## Configuration Questions

### 1. Testing Framework
- **Swift Testing** (Recommended, iOS 16+) - Modern, expressive syntax
- **XCTest** - Traditional framework, all iOS versions
- **Both** - Mix of frameworks

### 2. Test Types to Generate
- **Unit Tests** - Test individual components in isolation
- **Integration Tests** - Test component interactions
- **UI Tests** - Test user interface and flows
- **All** - Complete test coverage

### 3. Architecture Pattern
- **MVVM** - ViewModel tests
- **TCA** - Reducer tests
- **Repository** - Data layer tests
- **Custom** - Based on project structure

## Generated Files

### Unit Tests
```
Tests/UnitTests/
├── ViewModelTests/
│   └── ItemViewModelTests.swift
├── ServiceTests/
│   └── APIClientTests.swift
└── RepositoryTests/
    └── ItemRepositoryTests.swift
```

### UI Tests
```
Tests/UITests/
├── Screens/
│   └── HomeScreenTests.swift
├── Flows/
│   └── OnboardingFlowTests.swift
└── Helpers/
    └── TestHelpers.swift
```

## Swift Testing (Modern)

### Basic Test Structure

```swift
import Testing
@testable import YourApp

@Suite("Item ViewModel Tests")
struct ItemViewModelTests {

    @Test("loads items successfully")
    func loadsItems() async throws {
        let mockRepository = MockItemRepository()
        let viewModel = ItemViewModel(repository: mockRepository)

        await viewModel.loadItems()

        #expect(viewModel.items.count == 3)
        #expect(viewModel.isLoading == false)
    }

    @Test("handles empty state")
    func handlesEmptyState() async {
        let mockRepository = MockItemRepository(items: [])
        let viewModel = ItemViewModel(repository: mockRepository)

        await viewModel.loadItems()

        #expect(viewModel.items.isEmpty)
        #expect(viewModel.showEmptyState)
    }
}
```

### Parameterized Tests

```swift
@Test("validates email format", arguments: [
    ("valid@email.com", true),
    ("invalid", false),
    ("no@tld", false),
    ("test@domain.co.uk", true)
])
func validatesEmail(email: String, isValid: Bool) {
    #expect(EmailValidator.isValid(email) == isValid)
}
```

## XCTest (Traditional)

### Basic Test Structure

```swift
import XCTest
@testable import YourApp

final class ItemViewModelTests: XCTestCase {

    var sut: ItemViewModel!
    var mockRepository: MockItemRepository!

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

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

    func testLoadsItems() async throws {
        await sut.loadItems()

        XCTAssertEqual(sut.items.count, 3)
        XCTAssertFalse(sut.isLoading)
    }
}
```

## Test Patterns

### Testing ViewModels

```swift
@Suite("ViewModel Tests")
struct ViewModelTests {

    @Test("state transitions correctly")
    func stateTransitions() async {
        let vm = ItemViewModel(repository: MockItemRepository())

        #expect(vm.state == .idle)

        await vm.loadItems()

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

    @Test("error handling")
    func errorHandling() async {
        let failingRepo = MockItemRepository(shouldFail: true)
        let vm = ItemViewModel(repository: failingRepo)

        await vm.loadItems()

        #expect(vm.state == .error)
        #expect(vm.errorMessage != nil)
    }
}
```

### Testing Async Code

```swift
@Test("fetches data asynchronously")
func fetchesData() async throws {
    let service = APIService()

    let result = try await service.fetchItems()

    #expect(result.count > 0)
}

@Test("times out appropriately")
func timesOut() async {
    await #expect(throws: TimeoutError.self) {
        try await withTimeout(seconds: 1) {
            try await Task.sleep(for: .seconds(5))
        }
    }
}
```

## Mock Creation

### Protocol-Based Mocks

```swift
protocol ItemRepository {
    func fetchItems() async throws -> [Item]
    func saveItem(_ item: Item) async throws
}

final class MockItemRepository: ItemRepository {
    var items: [Item] = []
    var shouldFail = false
    var saveCallCount = 0

    func fetchItems() async throws -> [Item] {
        if shouldFail {
            throw TestError.mockFailure
        }
        return items
    }

    func saveItem(_ item: Item) async throws {
        saveCallCount += 1
        items.append(item)
    }
}
```

## UI Testing

### Screen Object Pattern

```swift
import XCTest

final class HomeScreen {
    let app: XCUIApplication

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

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

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

    func tapItem(at index: Int) {
        itemList.cells.element(boundBy: index).tap()
    }

    func addNewItem(title: String) {
        addButton.tap()
        app.textFields["itemTitle"].tap()
        app.textFields["itemTitle"].typeText(title)
        app.buttons["save"].tap()
    }
}
```

## Integration Steps

### 1. Add Test Target

In Xcode:
1. File > New > Target
2. Choose "Unit Testing Bundle" or "UI Testing Bundle"
3. Name appropriately (e.g., `YourAppTests`)

### 2. Configure Test Scheme

1. Edit Scheme > Test
2. Add test targets
3. Configure code coverage

### 3. Run Tests

```bash
# Command line
xcodebuild test -scheme YourApp -destination 'platform=iOS Simulator,name=iPhone 16'

# With coverage
xcodebuild test -scheme YourApp -enableCodeCoverage YES
```

## Best Practices

1. **Test one thing per test** - Clear, focused tests
2. **Use descriptive names** - Tests as documentation
3. **Arrange-Act-Assert** - Clear test structure
4. **Mock external dependencies** - Isolate units
5. **Test edge cases** - Empty, nil, error states
6. **Keep tests fast** - No real network/disk

## Top 5 Mistakes

| # | Mistake | Why It's Wrong | Fix |
|---|---------|---------------|-----|
| 1 | Testing implementation details instead of behavior | Tests break on every refactor, providing no safety net | Test public API and observable outcomes, not internal state |
| 2 | Sharing mutable state between tests | Tests pass individually but fail when run together (order-dependent) | Create fresh instances in each test; use `init()` in `@Suite` structs or `setUp()` in XCTest |
| 3 | Using `XCTAssertTrue(result != nil)` instead of `XCTUnwrap` | Failure message is useless ("XCTAssertTrue failed") with no context | Use `let value = try XCTUnwrap(result)` or `#expect(result != nil)` with Swift Testing |
| 4 | Not testing error paths | Only happy-path coverage; errors crash in production | Always test with `shouldFail = true` mocks and verify error state |
| 5 | Real network calls in unit tests | Tests are slow, flaky, and fail offline | Use protocol-based mocks; reserve real network calls for integration test schemes |

## Review Checklist

Before finishing test generation, verify:

- [ ] **Naming**: Test names describe the behavior, not the method (`loadsItemsSuccessfully` not `testLoadItems`)
- [ ] **Isolation**: Each test creates its own dependencies -- no shared mutable state
- [ ] **No real I/O**: Unit tests use mocks for network, disk, and database
- [ ] **Async handling**: Async tests use `async throws` (Swift Testing) or `async throws` with expectations (XCTest)
- [ ] **Error paths tested**: At least one test per function verifies error/failure behavior
- [ ] **Edge cases**: Empty collections, nil optionals, boundary values are tested
- [ ] **Assertions are specific**: Using `#expect(items.count == 3)` not `#expect(!items.isEmpty)`
- [ ] **Mock call verification**: Mocks track call counts and received arguments where needed
- [ ] **No force unwraps in tests**: Use `try #require()` (Swift Testing) or `XCTUnwrap` (XCTest)
- [ ] **Tests compile and run**: Verify with `xcodebuild test` or Xcode test navigator

## References

- **templates.md** -- Production-ready code templates for test suites, mocks, and helpers
- [Swift Testing](https://developer.apple.com/documentation/testing)
- [XCTest Framework](https://developer.apple.com/documentation/xctest)
- [Testing Your Apps in Xcode](https://developer.apple.com/documentation/xcode/testing-your-apps-in-xcode)

Related Skills

test-data-factory

110
from gustavscirulis/snapgrid

Generate test fixture factories for your models. Builder pattern and static factories for zero-boilerplate test data. Use when tests need sample data setup.

test-contract

110
from gustavscirulis/snapgrid

Generate protocol/interface test suites that any implementation must pass. Define the contract once, test every implementation. Use when designing protocols or swapping implementations.

snapshot-test-setup

110
from gustavscirulis/snapgrid

Set up SwiftUI visual regression testing with swift-snapshot-testing. Generates snapshot test boilerplate and CI configuration. Use for UI regression prevention.

integration-test-scaffold

110
from gustavscirulis/snapgrid

Generate cross-module test harness with mock servers, in-memory stores, and test configuration. Use when testing networking + persistence + business logic together.

characterization-test-generator

110
from gustavscirulis/snapgrid

Generates tests that capture current behavior of existing code before refactoring. Use when you need a safety net before AI-assisted refactoring or modifying legacy code.

testing

110
from gustavscirulis/snapgrid

TDD and testing skills for iOS/macOS apps. Covers characterization tests, TDD workflows, test contracts, snapshot tests, and test infrastructure. Use for test-driven development, adding tests to existing code, or building test infrastructure.

test-spec

110
from gustavscirulis/snapgrid

Generates comprehensive test specification with unit tests, UI tests, accessibility testing, and beta testing plan. Creates TEST_SPEC.md from PRD and implementation specs. Use when creating QA strategy.

prd-generator

110
from gustavscirulis/snapgrid

Generates comprehensive Product Requirements Document from product plan. Creates PRD.md with features, user stories, acceptance criteria, and success metrics. Use when creating product requirements.

idea-generator

110
from gustavscirulis/snapgrid

Brainstorm and rank iOS/macOS app ideas tailored to developer skills. Use when user says "what should I build", "give me app ideas", "I don't know what to build", "brainstorm app ideas", or "help me find an app idea".

beta-testing

110
from gustavscirulis/snapgrid

Beta testing strategy for iOS/macOS apps. Covers TestFlight program setup, beta tester recruitment, feedback collection methodology, user interviews, signal-vs-noise interpretation, and go/no-go launch readiness decisions. Use when planning a beta, setting up TestFlight, collecting user feedback, or deciding if ready to launch.

widget-generator

110
from gustavscirulis/snapgrid

Generate WidgetKit widgets for iOS/macOS home screen and lock screen with timeline providers, interactive elements, and App Intent configuration. Use when adding widgets to an app.

tipkit-generator

110
from gustavscirulis/snapgrid

Generate TipKit infrastructure with inline/popover tips, rules, display frequency, and testing utilities. Use when adding contextual tips or feature discovery to an iOS/macOS app.