---
name: rshankras/test-generator
source: https://app.decimal.ai/s/rshankras-test-generator@1/SKILL.md
source_sha256: 0e27ffd0cd20
---

# 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)