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
US web checkout via the StoreKit External Purchase Link entitlement — currently 0% Apple commission (litigation ongoing), how to ship it safely, and how to architect for a commission flip so a future ruling is a config change, not a rewrite. Use when adding external purchase links, weighing web checkout vs IAP, or planning US-storefront pricing strategy.
tools
Revenue beyond the single-app price tag — own-app bundles, Family Sharing as a conversion lever, cross-developer bundles & suites, and institutional licensing via Group Purchases / Apple School & Business Manager. Use when a developer has multiple apps, a subscription worth sharing, complementary indie partners, or school/clinic/business buyers.
testing
Run a structured accessibility audit on an iOS/macOS app — automated XCUITest audits, Accessibility Inspector, manual VoiceOver/Dynamic Type passes, and App Store Accessibility Nutrition Label evaluation. Use before release, when preparing Nutrition Label declarations, or for EU Accessibility Act compliance.
tools
Stage-by-stage audit of an app's App Store growth machinery against a 54-item P0–P9 playbook — every item scored from an App Store Connect MCP call, a codebase check, or an explicit question to the user, then routed to the skill or command that fixes it. Read-only on App Store Connect. Use for a growth audit or scorecard, a pre-launch growth plan, a quarterly re-audit, or "which growth levers am I missing."