Generate Tests logo

Generate Tests

Community
Terryc21
generate-tests

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

Overview

PublisherTerryc21
Repositorysitrep
Skill namegenerate-tests
Stars
50
Forks
9
Bundled files
Instructions only
LicenseApache-2.0
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by Terryc21 on GitHub. Read the source before you install it.

Installation

Install the Generate Tests AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
    https://github.com/Terryc21/sitrep/tree/main/skills/generate-tests
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/Terryc21/sitrep.git /tmp/sitrep
mkdir -p .claude/skills
cp -r /tmp/sitrep/skills/generate-tests .claude/skills/generate-tests
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Generate Tests in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Generate Tests on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Generate Tests is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

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

bash
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:

bash
# 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
bash
# 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:

markdown
## 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:

swift
@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

swift
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

swift
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:

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

bash
# 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:

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

swift
@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

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

Testing Errors (Swift Testing)

swift
@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

Frequently asked questions

What does the Generate Tests AI skill do?

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

Why use Generate Tests on TypingMind?

Because you install it once and use it with any model. Generate Tests is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Generate Tests in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/Terryc21/sitrep/tree/main/skills/generate-tests. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Generate Tests?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Generate Tests?

As many as you like. As long as a model supports skills, you can use Generate Tests with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Generate Tests AI skill free?

Yes. It is published on GitHub by Terryc21 under the Apache-2.0 license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇