.claude/skills/generators/test-generator/SKILL.md
Generate XCTest templates for unit tests, integration tests, and UI tests. Use when adding test coverage to ViewModels, services, or repositories.
npx skillsauth add brdohman/agile-maestro test-generatorInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Generate test templates for unit tests, integration tests, and UI tests in iOS/macOS apps.
Before generating, verify:
Existing Test Targets
# Check for test targets
find . -name "*Tests" -type d | head -5
grep -r "testTarget" Package.swift 2>/dev/null
Testing Frameworks
# Check for Swift Testing or XCTest usage
grep -r "import XCTest\|import Testing" --include="*.swift" | head -5
Project Architecture
# Identify patterns (MVVM, TCA, etc.)
grep -r "ViewModel\|Reducer\|UseCase" --include="*.swift" | head -5
Before generating tests, verify the behavior is not already covered:
Equatable, Hashable, CaseIterable, or Codable conformances synthesized by the compiler[(input, expected)]) instead of N separate methods.claude/rules/global/testing-requirements.md "When NOT to Write Tests" for the full listTests/UnitTests/
├── ViewModelTests/
│ └── ItemViewModelTests.swift
├── ServiceTests/
│ └── APIClientTests.swift
└── RepositoryTests/
└── ItemRepositoryTests.swift
Tests/UITests/
├── Screens/
│ └── HomeScreenTests.swift
├── Flows/
│ └── OnboardingFlowTests.swift
└── Helpers/
└── TestHelpers.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)
}
}
@Test("validates email format", arguments: [
("[email protected]", true),
("invalid", false),
("no@tld", false),
("[email protected]", true)
])
func validatesEmail(email: String, isValid: Bool) {
#expect(EmailValidator.isValid(email) == isValid)
}
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)
}
}
@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)
}
}
@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))
}
}
}
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)
}
}
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()
}
}
In Xcode:
YourAppTests)# Command line
xcodebuild test -scheme YourApp -destination 'platform=iOS Simulator,name=iPhone 16'
# With coverage
xcodebuild test -scheme YourApp -enableCodeCoverage YES
REQUIRED: All test data must use TestFixtures helpers. Never hardcode passwords, tokens, or secrets inline. SAST tools (Aikido) flag hardcoded credentials as real findings even in tests.
Create reusable test data factories instead of inline construction:
enum TestFixtures {
// Credentials — generate programmatically, never use real-looking strings
static func validPassword() -> String { String(repeating: "Aa1!", count: 3) }
static func weakPassword() -> String { String(repeating: "a", count: 3) }
static func validToken() -> String { String(repeating: "t", count: 32) }
static func validEmail() -> String { "[email protected]" }
// Domain objects
static func item(title: String = "Test Item", status: String = "active") -> Item {
Item(id: UUID(), title: title, status: status, createdAt: Date())
}
static func items(count: Int = 5) -> [Item] {
(0..<count).map { item(title: "Item \($0)") }
}
}
Check if the project already has test helpers (e.g., TestFixtures.swift) before creating new ones — reuse and extend existing helpers.
Each test must start with clean state. Never depend on execution order:
setUp() to create fresh instancestearDown() to nil out all propertiesNSInMemoryStoreTypefunc testListLoadPerformance() {
let options = XCTMeasureOptions()
options.iterationCount = 5
measure(options: options) {
// Operation to measure
sut.loadItems()
}
}
// Set baselines in Xcode: Edit Scheme > Test > Options > Performance
Use XCTMetric for specific measurements:
XCTClockMetric — wall clock timeXCTMemoryMetric — memory usageXCTCPUMetric — CPU timeSigns of a flaky test:
Task.sleep or DispatchQueue.asyncAfter for timingFixes:
XCTestExpectation or async/awaitsetUp/tearDown@MainActor for UI state testsCoverage thresholds: 100% ViewModels/business logic, 80% Services.
What coverage numbers actually mean:
if tested?)# Extract coverage after test run
xcrun xccov view --report --json DerivedData/Logs/Test/*.xcresult
After a bug fix:
When to run the full suite vs. targeted:
// BAD — shared state between tests
static var sharedViewModel = ViewModel()
// GOOD — fresh instance per test
var sut: ViewModel!
override func setUp() { sut = ViewModel(service: MockService()) }
override func tearDown() { sut = nil }
Core Data isolation:
// Each test gets its own in-memory container
override func setUp() {
let container = NSPersistentContainer(name: "Model")
let desc = NSPersistentStoreDescription()
desc.type = NSInMemoryStoreType
container.persistentStoreDescriptions = [desc]
container.loadPersistentStores { _, _ in }
context = container.viewContext
}
Test multiple inputs without duplicating test methods:
// Swift Testing (preferred)
@Test("validates email", arguments: [
("[email protected]", true),
("invalid", false),
("", false),
("[email protected]", false)
])
func validatesEmail(email: String, expected: Bool) {
#expect(Validator.isValidEmail(email) == expected)
}
// XCTest fallback — loop with context
func testEmailValidation() {
let cases: [(input: String, valid: Bool)] = [
("[email protected]", true), ("invalid", false), ("", false)
]
for testCase in cases {
XCTAssertEqual(Validator.isValidEmail(testCase.input), testCase.valid,
"Failed for input: \(testCase.input)")
}
}
testing
XCTest patterns for macOS Swift apps. Unit tests, async tests, Core Data tests, mock patterns, and assertion reference. Use when writing or reviewing tests.
tools
How to transition workflow state between review stages. Rules for setting review_stage and review_result fields on Stories and Epics.
documentation
Comment structure and rules for task workflow updates. Use when adding any comment to a task during implementation, review, or fix cycles.
testing
Validate task/story/epic/bug/techdebt metadata against schema v2.0. Run after TaskCreate or TaskUpdate to verify compliance. Returns pass/fail with actionable details.