skills/generators/test-generator/SKILL.md
Generate test templates for unit tests, integration tests, and UI tests using Swift Testing and XCTest. Use when adding tests to iOS/macOS apps.
npx skillsauth add rshankras/claude-code-apple-skills 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.
Use this skill when the user:
@Test, #expect, @Suite)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
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:
If a test target already exists:
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.).
Tests/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
| # | 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 |
Before finishing test generation, verify:
loadsItemsSuccessfully not testLoadItems)async throws (Swift Testing) or async throws with expectations (XCTest)#expect(items.count == 3) not #expect(!items.isEmpty)try #require() (Swift Testing) or XCTUnwrap (XCTest)xcodebuild test or Xcode test navigatordevelopment
Build, install, and launch an iOS app on a physical iPhone or iPad entirely from the command line (no Xcode GUI), using xcodebuild + devicectl. Use when the user wants to run, test, or screenshot their app on a real device without opening Xcode.
development
Comprehensive iOS development guidance including Swift best practices, SwiftUI patterns, UI/UX review against HIG, and app planning. Use for iOS code review, best practices, accessibility audits, or planning new iOS apps.
development
Build, install, launch, and screenshot an iOS app in the Simulator to verify a change visually. Use when the user wants to run the app, see a change live, screenshot the running app, or confirm a UI fix actually works (not just that it compiles).
development
Audits skills in this repo for consistency, API drift, and structural gaps. Produces a prioritized report grouped by severity (Critical/High/Medium/Low). Use when asked to "audit skills", "check the skill repo for drift", or when planning bulk skill cleanup. Read-only — does not apply fixes.