agentleFS
Terryc21Terryc21/sitrep/skills/generate-tests/SKILL.md

generate-tests

Skill50 starsChanged 9 months agoApache-2.0View on GitHub

Generate unit and UI tests for specified code with edge cases and mocks. Triggers: "generate tests", "write tests", "add tests", "test coverage".

We split this skill into the rules it gives an agent

7 rules. 0 are common, 7 are distinctive, and 0 are very specific to this project.

  • 0

    Common

    Most of the 7 edited versions we read keep these.

  • 7

    Distinctive

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    If uncommitted changes exist:

    line 27
  2. Distinctive:

    If "Commit first": Ask for a commit message, stage changed files, and commit.

    line 44
  3. Distinctive:

    If both are present:

    line 66
  4. Distinctive:

    Read the target code and extract:

    line 117
  5. Distinctive:

    Before writing tests, create a plan:

    line 140
  6. Distinctive:

    Write the generated tests to match the existing directory structure and naming conventions.

    line 297
  7. Distinctive:

    If tests fail, fix the issues before presenting results.

    line 310

“Common” is measured: kept by at least 6 in 10 of the 7 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Generate Tests

Quick Ref: Generate unit and UI tests with proper mocking and edge case coverage. Outputs directly to Tests/ directory.

YOU MUST EXECUTE THIS WORKFLOW. Do not just describe it.


Pre-flight: Git Safety Check

git status --short

If uncommitted changes exist:

AskUserQuestion with questions:
[
  {
    "question": "You have uncommitted changes. Commit before proceeding?",
    "header": "Git",
    "options": [
      {"label": "Commit first (Recommended)", "description": "Save current work so you can revert if this skill modifies files"},
      {"label": "Continue without committing", "description": "Proceed — I accept the risk"}
    ],
    "multiSelect": false
  }
]

If "Commit first": Ask for a commit message, stage changed files, and commit. Then proceed.


Step 1: Detect Test Framework

Auto-detect which testing framework the project uses:

# Check for Swift Testing
Grep pattern="import Testing" glob="**/*Test*.swift" output_mode="count"

# Check for XCTest
Grep pattern="import XCTest" glob="**/*Test*.swift" output_mode="count"
DetectionFrameworkSyntax
import Testing foundSwift Testing@Test, #expect, @Suite
Only import XCTestXCTestXCTestCase, XCTAssert*
New project (no tests)Swift Testing (Recommended)Modern, concurrent-safe

If both are present:

AskUserQuestion with questions:
[
  {
    "question": "Which testing framework should I use?",
    "header": "Framework",
    "options": [
      {"label": "Swift Testing (Recommended)", "description": "Modern framework with @Test, #expect, better async support"},
      {"label": "XCTest", "description": "Classic framework with XCTestCase, XCTAssert*"}
    ],
    "multiSelect": false
  }
]

Step 2: Gather Test Requirements

AskUserQuestion with questions:
[
  {
    "question": "What would you like to generate tests for?",
    "header": "Target",
    "options": [
      {"label": "Specific type/file", "description": "I'll provide the name or path"},
      {"label": "Analyze coverage gaps", "description": "Find untested code and suggest tests"},
      {"label": "Feature area", "description": "Generate tests for a feature (e.g., 'authentication')"}
    ],
    "multiSelect": false
  },
  {
    "question": "What type of tests do you need?",
    "header": "Test type",
    "options": [
      {"label": "Unit tests", "description": "Test individual functions/methods in isolation"},
      {"label": "UI tests", "description": "Test user interactions and flows"},
      {"label": "All applicable", "description": "Generate appropriate tests for each layer"}
    ],
    "multiSelect": true
  }
]

Step 3: Analyze Target Code

Read the target code and extract:

  1. Public API Surface — public/internal functions, initializers, computed properties
  2. Dependencies — protocol dependencies (need mocks), external services (need stubs)
  3. Side Effects — network calls, file I/O, UserDefaults, notifications
  4. State Transitions — how internal state changes, what triggers transitions
# Find the target file
Glob pattern="**/*TargetName*.swift"

# Read the target
Read file_path="<path_to_target>"

# Find existing tests for this target
Glob pattern="**/*TargetName*Tests.swift"
Glob pattern="**/*TargetName*Test.swift"

Step 4: Generate Test Plan

Before writing tests, create a plan:

## Test Plan for [TargetType]

### Unit Tests Needed

| Test Case | Input | Expected Output | Priority |
|-----------|-------|-----------------|----------|
| init_withValidData_succeeds | Valid params | Instance created | High |
| init_withMissingRequired_throws | nil required | ValidationError | High |
| fetch_whenEmpty_returnsEmptyArray | Mock empty | [] | Medium |

### Mocks Required

| Protocol | Mock Name | Behavior to Simulate |
|----------|-----------|---------------------|
| NetworkService | MockNetworkService | Success, failure, timeout |

### Edge Cases

| Scenario | Test Name |
|----------|-----------|
| Empty input | test_withEmptyInput_handlesGracefully |
| Nil optional | test_withNilOptional_usesDefault |
| Concurrent access | test_concurrentCalls_threadSafe |

Step 5: Generate Mock Implementations

For each protocol dependency, generate a mock:

@testable import YourApp

final class MockNetworkService: NetworkServiceProtocol {
    var fetchResult: Result<[Item], Error> = .success([])
    var fetchCallCount = 0

    func fetch<T: Decodable>(from url: URL) async throws -> T {
        fetchCallCount += 1
        switch fetchResult {
        case .success(let data):
            guard let result = data as? T else {
                throw MockError.typeMismatch
            }
            return result
        case .failure(let error):
            throw error
        }
    }

    enum MockError: Error {
        case typeMismatch
    }
}

Step 6: Generate Tests

Swift Testing Format

import Testing
@testable import YourApp

@Suite("ItemViewModel Tests")
struct ItemViewModelTests {

    @Test("Initialize with valid data succeeds")
    func init_withValidData_succeeds() throws {
        let viewModel = ItemViewModel(item: .preview)
        #expect(viewModel.title == "Preview Item")
        #expect(viewModel.isLoading == false)
    }

    @Test("Fetch items when network available returns items")
    func fetch_whenNetworkAvailable_returnsItems() async throws {
        let mockService = MockNetworkService()
        mockService.fetchResult = .success([Item.preview])
        let viewModel = ItemViewModel(networkService: mockService)
        try await viewModel.fetchItems()
        #expect(viewModel.items.count == 1)
    }

    @Test("Fetch items when network fails throws error")
    func fetch_whenNetworkFails_throwsError() async {
        let mockService = MockNetworkService()
        mockService.fetchResult = .failure(NetworkError.noConnection)
        let viewModel = ItemViewModel(networkService: mockService)
        await #expect(throws: NetworkError.self) {
            try await viewModel.fetchItems()
        }
    }
}

XCTest Format

import XCTest
@testable import YourApp

final class ItemViewModelTests: XCTestCase {

    var sut: ItemViewModel!
    var mockService: MockNetworkService!

    override func setUp() {
        super.setUp()
        mockService = MockNetworkService()
        sut = ItemViewModel(networkService: mockService)
    }

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

    func test_init_withValidData_succeeds() {
        let viewModel = ItemViewModel(item: .preview)
        XCTAssertEqual(viewModel.title, "Preview Item")
        XCTAssertFalse(viewModel.isLoading)
    }

    func test_fetch_whenNetworkFails_throwsError() async {
        mockService.stubbedFetchResult = .failure(NetworkError.noConnection)
        do {
            try await sut.fetchItems()
            XCTFail("Expected error to be thrown")
        } catch {
            XCTAssertTrue(error is NetworkError)
        }
    }
}

Step 7: Write Tests to Project

Determine the correct test file location:

# Find existing test directory structure
Glob pattern="**/*Tests.swift"

# Common patterns:
# Tests/AppTests/ViewModels/ItemViewModelTests.swift
# Tests/UnitTests/ItemViewModelTests.swift

Write the generated tests to match the existing directory structure and naming conventions.


Step 8: Verify and Present Summary

8.1: Run Tests

# Verify generated tests compile and pass
xcodebuild test -scheme <SCHEME> -destination 'platform=iOS Simulator,name=iPhone 16' -only-testing:<TEST_TARGET> -quiet 2>&1 | tail -10

If tests fail, fix the issues before presenting results.

8.2: Display Results

Display the summary inline so the user sees results immediately:

## Test Generation Complete

**Target:** ItemViewModel.swift
**Framework:** Swift Testing
**Tests Generated:** N
**Status:** All passing ✓

| Test Type | Count | File |
|-----------|-------|------|
| Unit Tests | 10 | Tests/ItemViewModelTests.swift |
| Mock | 1 | Tests/Mocks/MockNetworkService.swift |

**Coverage Added:**
- Initialization: 2 tests
- Fetch operations: 3 tests
- Error handling: 2 tests
- Edge cases: 3 tests

Test Naming Convention

Follow: [method]_[scenario]_[expectedBehavior]

Examples:

  • fetchItems_whenNetworkAvailable_returnsItems
  • saveItem_withEmptyTitle_throwsValidationError
  • init_withNilOptional_usesDefaultValue
  • delete_whenItemExists_removesFromList

Common Test Patterns

Parameterized Tests (Swift Testing)

@Test(arguments: [
    ("valid@email.com", true),
    ("invalid", false),
    ("", false),
    ("a@b.c", true)
])
func validateEmail_withInput_returnsExpected(email: String, expected: Bool) {
    #expect(sut.isValidEmail(email) == expected)
}

Testing @MainActor Code

@Test @MainActor func updateUI_onMainThread() {
    sut.updateTitle("New Title")
    #expect(sut.displayTitle == "New Title")
}

Testing Errors (Swift Testing)

@Test func invalidInput_throwsValidationError() async {
    await #expect(throws: ValidationError.self) {
        try await sut.validate(input: "")
    }
}

Troubleshooting

ProblemSolution
Can't find test directoryCheck for Tests/ or *Tests/ at project root
Target has no protocol dependenciesTest public methods directly without mocks
@MainActor prevents test creationAdd @MainActor to test function or use MainActor.run {}
SwiftData ModelContext in testsCreate in-memory container: ModelContainer(for:, configurations: .init(isStoredInMemoryOnly: true))
Tests can't import app moduleVerify test target has the app module in its dependencies

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.