generate-tests
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"
| Detection | Framework | Syntax |
|---|---|---|
import Testing found |
Swift Testing | @Test, #expect, @Suite |
Only import XCTest |
XCTest | XCTestCase, 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:
- Public API Surface — public/internal functions, initializers, computed properties
- Dependencies — protocol dependencies (need mocks), external services (need stubs)
- Side Effects — network calls, file I/O, UserDefaults, notifications
- 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_returnsItemssaveItem_withEmptyTitle_throwsValidationErrorinit_withNilOptional_usesDefaultValuedelete_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
| Problem | Solution |
|---|---|
| Can't find test directory | Check for Tests/ or *Tests/ at project root |
| Target has no protocol dependencies | Test public methods directly without mocks |
| @MainActor prevents test creation | Add @MainActor to test function or use MainActor.run {} |
| SwiftData ModelContext in tests | Create in-memory container: ModelContainer(for:, configurations: .init(isStoredInMemoryOnly: true)) |
| Tests can't import app module | Verify test target has the app module in its dependencies |