rshankras/test-data-factory
Generate test fixture factories for your models. Builder pattern and static factories for zero-boilerplate test data. Use when tests need sample data setup.
npx skills add https://github.com/rshankras/claude-code-apple-skills --skill test-data-factory
Generate factory helpers that make creating test data effortless. Eliminates boilerplate in test setup so writing tests has zero friction.
Use this skill when the user:
// ❌ Without factory — every test repeats this
let item = Item(
id: UUID(),
title: "Test Item",
description: "A test description",
category: .general,
createdAt: Date(),
updatedAt: Date(),
isFavorite: false,
tags: [],
author: User(id: UUID(), name: "Test User", email: "[email protected]")
)
// ✅ With factory — one line, override only what matters
let item = Item.fixture()
let favoriteItem = Item.fixture(isFavorite: true)
let taggedItem = Item.fixture(tags: ["swift", "testing"])
Glob: **/*.swift (in source targets)
Grep: "struct.*:.*Identifiable|class.*:.*Identifiable|@Model"
Grep: "struct.*:.*Codable|struct.*:.*Sendable"
Identify models that appear in test files:
Grep: "let.*=.*Model(" in test targets (manual construction)
Ask via AskUserQuestion:
// Tests/Factories/Item+Factory.swift
import Foundation
@testable import YourApp
extension Item {
/// Creates a test fixture with sensible defaults.
/// Override only the properties relevant to your test.
static func fixture(
id: UUID = UUID(),
title: String = "Test Item",
description: String = "A test description",
category: Category = .general,
createdAt: Date = Date(timeIntervalSince1970: 1_700_000_000),
updatedAt: Date = Date(timeIntervalSince1970: 1_700_000_000),
isFavorite: Bool = false,
tags: [String] = [],
author: User = .fixture()
) -> Item {
Item(
id: id,
title: title,
description: description,
category: category,
createdAt: createdAt,
updatedAt: updatedAt,
isFavorite: isFavorite,
tags: tags,
author: author
)
}
/// Named fixtures for common test scenarios
static var sample: Item { .fixture() }
static var favorite: Item { .fixture(isFavorite: true) }
static var empty: Item { .fixture(title: "", description: "") }
/// Collection fixtures
static var sampleList: [Item] {
[
.fixture(id: UUID(), title: "First Item", category: .work),
.fixture(id: UUID(), title: "Second Item", category: .personal),
.fixture(id: UUID(), title: "Third Item", category: .general)
]
}
}
extension User {
static func fixture(
id: UUID = UUID(),
name: String = "Test User",
email: String = "[email protected]"
) -> User {
User(id: id, name: name, email: email)
}
static var sample: User { .fixture() }
}
For models with many optional fields or complex relationships:
// Tests/Factories/ItemBuilder.swift
@testable import YourApp
final class ItemBuilder {
private var id: UUID = UUID()
private var title: String = "Test Item"
private var description: String = "A test description"
private var category: Category = .general
private var createdAt: Date = .init(timeIntervalSince1970: 1_700_000_000)
private var isFavorite: Bool = false
private var tags: [String] = []
private var author: User = .fixture()
@discardableResult
func with(title: String) -> Self {
self.title = title
return self
}
@discardableResult
func with(category: Category) -> Self {
self.category = category
return self
}
@discardableResult
func favorited() -> Self {
self.isFavorite = true
return self
}
@discardableResult
func with(tags: [String]) -> Self {
self.tags = tags
return self
}
@discardableResult
func authored(by user: User) -> Self {
self.author = user
return self
}
func build() -> Item {
Item(
id: id,
title: title,
description: description,
category: category,
createdAt: createdAt,
updatedAt: createdAt,
isFavorite: isFavorite,
tags: tags,
author: author
)
}
}
// Usage:
let item = ItemBuilder()
.with(title: "Important")
.with(category: .work)
.favorited()
.build()
For generating unique test data in loops:
extension Item {
/// Creates N unique items with sequential titles
static func fixtures(count: Int) -> [Item] {
(0..<count).map { index in
.fixture(
id: UUID(),
title: "Item \(index + 1)"
)
}
}
/// Creates items matching specific states for state-based testing
static var allStates: [Item] {
[
.fixture(title: "Draft", category: .draft),
.fixture(title: "Active", category: .active),
.fixture(title: "Archived", category: .archived),
.fixture(title: "Deleted", category: .deleted)
]
}
}
Provide fixed reference dates:
// Tests/Factories/Date+Factory.swift
extension Date {
/// Fixed reference dates for deterministic tests
static let testReference = Date(timeIntervalSince1970: 1_700_000_000) // 2023-11-14
static let testYesterday = testReference.addingTimeInterval(-86_400)
static let testLastWeek = testReference.addingTimeInterval(-604_800)
static let testNextMonth = testReference.addingTimeInterval(2_592_000)
/// Create a date relative to test reference
static func testDate(daysFromReference days: Int) -> Date {
testReference.addingTimeInterval(TimeInterval(days * 86_400))
}
}
For network/API testing:
// Tests/Factories/APIResponse+Factory.swift
extension APIResponse where T == [Item] {
static func success(items: [Item] = Item.sampleList) -> APIResponse {
APIResponse(data: items, statusCode: 200, error: nil)
}
static func empty() -> APIResponse {
APIResponse(data: [], statusCode: 200, error: nil)
}
static func error(_ error: APIError = .serverError) -> APIResponse {
APIResponse(data: nil, statusCode: 500, error: error)
}
static func notFound() -> APIResponse {
APIResponse(data: nil, statusCode: 404, error: .notFound)
}
}
| Property Type | Default Strategy |
|--------------|-----------------|
| UUID | UUID() |
| String | Descriptive placeholder ("Test Item") |
| Date | Fixed timestamp, not Date() |
| Bool | false |
| Array | Empty [] (opt-in to populated) |
| Optional | nil |
| Enum | Most common case |
| Nested model | That model's .fixture() |
// Static factory — use .fixture() for customizable, .sample for quick
Item.fixture(title: "Custom") // Customizable
Item.sample // Quick default
Item.sampleList // Collection
// Named scenarios
Item.favorite // Specific state
Item.expired // Specific state
Item.empty // Edge case
// Builder — use descriptive method names
ItemBuilder().favorited().build()
ItemBuilder().with(title: "X").build()
## Test Data Factories Generated
### Models Covered
| Model | Factory Type | Named Fixtures | Collection Fixtures |
|-------|-------------|----------------|-------------------|
| Item | Static + Builder | sample, favorite, empty | sampleList, fixtures(count:) |
| User | Static | sample | — |
| APIResponse | Static | success, empty, error | — |
### Files Created
- `Tests/Factories/Item+Factory.swift`
- `Tests/Factories/User+Factory.swift`
- `Tests/Factories/ItemBuilder.swift`
- `Tests/Factories/Date+Factory.swift`
- `Tests/Factories/APIResponse+Factory.swift`
### Usage Example
// Before (30 lines of setup)
let user = User(id: UUID(), name: "Test", email: "[email protected]")
let item = Item(id: UUID(), title: "T", description: "D", ...)
// After (1 line)
let item = Item.fixture(isFavorite: true)
generators/test-generator/ — generates tests that use these factoriestesting/tdd-feature/ — TDD workflow benefits from low-friction factoriestesting/integration-test-scaffold/ — integration tests need realistic dataTake rshankras/test-data-factory from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.