一键导入
builder-factory
Generate test-data-bot factory builders for TypeScript types to create mock data for tests and Storybook
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Generate test-data-bot factory builders for TypeScript types to create mock data for tests and Storybook
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | builder-factory |
| version | 2.0.0 |
| description | Generate test-data-bot factory builders for TypeScript types to create mock data for tests and Storybook |
| tags | ["testing","factories","mock-data","test-data-bot","faker","typescript"] |
| author | Szum Tech Team |
| examples | ["Create a builder for User type","Generate builder for my Budget model","Build a builder for the Onboarding type with all relationships","Create builders for Product and Order types"] |
Generate test-data-bot factory builders for TypeScript types to create mock data for tests and Storybook stories.
This skill helps you create builders using @jackfranklin/test-data-bot and @faker-js/faker/locale/pl for generating realistic Polish-localized mock data. Builders are used for:
When the user provides a TypeScript type/interface or asks to create a factory for a model:
Analyze the Type Structure
Determine Builder Location
features/[feature-name]/test/builders/tests/builders/Builder Naming Convention
CRITICAL: Builder names MUST exactly match the type name they build.
Follow these naming rules:
Examples:
// ✅ CORRECT - matches exact type name
export type OnboardingProducts = ProductsFormData;
export const onboardingProductsBuilder = build<OnboardingProducts>({ ... });
// File: onboarding-products.builder.ts
export type OnboardingPreferences = PreferencesFormData;
export const onboardingPreferencesBuilder = build<OnboardingPreferences>({ ... });
// File: onboarding-preferences.builder.ts
export type BudgetTemplate = WithDates<BudgetTemplateBase>;
export const budgetTemplateBuilder = build<BudgetTemplate>({ ... });
// File: budget-template.builder.ts
export type UserProfile = WithDates<UserProfileBase>;
export const userProfileBuilder = build<UserProfile>({ ... });
// File: user-profile.builder.ts
// ❌ INCORRECT - uses base type name
export type OnboardingProducts = ProductsFormData;
export const productsBuilder = build<OnboardingProducts>({ ... }); // WRONG!
export type OnboardingPreferences = PreferencesFormData;
export const preferencesBuilder = build<OnboardingPreferences>({ ... }); // WRONG!
Special cases:
For Firebase Base types (without timestamps), use the base type name:
export const onboardingBaseBuilder = build<OnboardingBase>({ ... });
export const budgetTemplateBaseBuilder = build<BudgetTemplateBase>({ ... });
For main application types (with timestamps), use the exact type name:
export const onboardingBuilder = build<Onboarding>({ ... });
export const budgetTemplateBuilder = build<BudgetTemplate>({ ... });
Generate Builder Code
Follow this template structure:
import { build, sequence, perBuild } from "@jackfranklin/test-data-bot";
import { faker } from "@faker-js/faker/locale/pl";
// Import the type definition
import type { YourType } from "~/features/[feature]/types/your-type";
// Import any related builders for associations
import { relatedBuilder } from "./related.builder";
/**
* Builder for generating YourType test data.
*
* @example
* // Basic usage (using .one())
* const item = yourTypeBuilder.one();
*
* @example
* // Alternative usage (direct call)
* const item = yourTypeBuilder();
*
* @example
* // Override specific fields
* const customItem = yourTypeBuilder.one({
* overrides: {
* fieldName: "custom value"
* }
* });
*
* @example
* // Generate multiple items
* const items = Array.from({ length: 5 }, () => yourTypeBuilder.one());
*
* @example
* // Using traits
* const activeItem = yourTypeBuilder.one({ traits: ["active"] });
*/
export const yourTypeBuilder = build<YourType>({
fields: {
// sequence() - auto-incremented number
id: sequence(),
// perBuild() - function called each time
name: perBuild(() => faker.person.fullName()),
// Static values
status: "active"
// ... other fields
}
});
Field Mapping Guidelines
Use appropriate methods for each field type:
Identifiers:
id: sequence() - Auto-incremented number (1, 2, 3, ...)id: sequence((n) => \user-${n}`)` - Custom sequence with prefixuuid: perBuild(() => faker.string.uuid()) - Random UUIDslug: perBuild(() => faker.helpers.slugify(faker.lorem.words(3)))Personal Data:
firstName: perBuild(() => faker.person.firstName())lastName: perBuild(() => faker.person.lastName())email: perBuild(() => faker.internet.email())email: sequence((n) => \user${n}@test.com`)` - Unique sequential emailsphone: perBuild(() => faker.phone.number())avatar: perBuild(() => faker.image.avatar())Addresses:
street: perBuild(() => faker.location.streetAddress())city: perBuild(() => faker.location.city())zipCode: perBuild(() => faker.location.zipCode())country: "Polska" - Static value (no perBuild needed)Commerce:
productName: perBuild(() => faker.commerce.productName())price: perBuild(() => parseFloat(faker.commerce.price({ min: 10, max: 1000 })))currency: "PLN" - Static valuecategory: perBuild(() => faker.commerce.department())Text Content:
title: perBuild(() => faker.lorem.sentence())description: perBuild(() => faker.lorem.paragraph())text: perBuild(() => faker.lorem.text())Numbers:
amount: perBuild(() => faker.number.float({ min: 0, max: 1000, fractionDigits: 2 }))count: perBuild(() => faker.number.int({ min: 0, max: 100 }))percentage: perBuild(() => faker.number.int({ min: 0, max: 100 }))Dates:
createdAt: perBuild(() => faker.date.past())updatedAt: perBuild(() => faker.date.recent())scheduledAt: perBuild(() => faker.date.future())birthDate: perBuild(() => faker.date.birthdate({ min: 18, max: 65, mode: 'age' }))Booleans:
isActive: perBuild(() => faker.datatype.boolean())isPredefined: true - Static value (no perBuild needed)Enums/Unions:
status: perBuild(() => faker.helpers.arrayElement(["active", "inactive", "pending"]))role: perBuild(() => faker.helpers.arrayElement(["admin", "user", "guest"] as const))Arrays:
tags: perBuild(() => faker.helpers.arrayElements(["tag1", "tag2", "tag3"], { min: 1, max: 3 }))items: perBuild(() => Array.from({ length: 3 }, () => itemBuilder.one()))images: perBuild(() => Array.from({ length: 3 }, () => faker.image.url()))Objects/Relations:
address: perBuild(() => addressBuilder.one())Handle Complex Patterns
Traits (for variants):
export const userBuilder = build<User>({
fields: {
id: sequence(),
email: perBuild(() => faker.internet.email()),
firstName: perBuild(() => faker.person.firstName()),
lastName: perBuild(() => faker.person.lastName()),
role: "user",
isActive: true
},
// OFFICIAL test-data-bot traits API
traits: {
admin: {
overrides: {
role: "admin",
email: perBuild(() => faker.internet.email({ provider: "company.com" }))
}
},
inactive: {
overrides: {
isActive: false
}
},
guest: {
overrides: {
role: "guest",
firstName: "Guest",
lastName: perBuild(() => faker.string.numeric(4))
}
}
}
});
// Usage: userBuilder.one({ traits: ["admin"] })
// Multiple traits: userBuilder.one({ traits: ["admin", "inactive"] })
postBuild Hook (for computed fields and post-processing):
export const orderBuilder = build<Order>({
fields: {
id: sequence(),
userId: sequence(),
products: perBuild(() => Array.from({ length: 3 }, () => productBuilder.one())),
totalAmount: 0, // Will be computed in postBuild
status: perBuild(() => faker.helpers.arrayElement(["pending", "processing"])),
createdAt: perBuild(() => faker.date.recent()),
shippingAddress: perBuild(() => addressBuilder.one())
},
// postBuild - hook called AFTER building object
postBuild: (order) => {
// Compute totalAmount from products
order.totalAmount = order.products.reduce((sum, p) => sum + p.price, 0);
// Lowercase email
order.email = order.email.toLowerCase();
return order;
},
traits: {
bigOrder: {
overrides: {
products: perBuild(() => Array.from({ length: 10 }, () => productBuilder.one()))
}
}
}
});
Nested Builders (relationships):
// Address builder
export const addressBuilder = build<Address>({
fields: {
street: perBuild(() => faker.location.streetAddress()),
city: perBuild(() => faker.location.city()),
zipCode: perBuild(() => faker.location.zipCode()),
country: "Polska"
}
});
// User builder with address relationship
export const userBuilder = build<User>({
fields: {
id: sequence(),
name: perBuild(() => faker.person.fullName()),
// Use nested builder
address: perBuild(() => addressBuilder.one())
}
});
// Can override nested objects
const user = userBuilder.one({
overrides: {
address: addressBuilder.one({ overrides: { city: "Warszawa" } })
}
});
Create Test Examples
Always include usage examples in comments or a separate test file:
/**
* @example
* // Basic usage with .one()
* const user = userBuilder.one();
*
* @example
* // Alternative usage (direct call)
* const user = userBuilder();
*
* @example
* // Override fields
* const admin = userBuilder.one({
* overrides: { role: "admin" }
* });
*
* @example
* // Multiple instances
* const users = Array.from({ length: 10 }, () => userBuilder.one());
*
* @example
* // Using traits
* const admin = userBuilder.one({ traits: ["admin"] });
* const inactiveAdmin = userBuilder.one({ traits: ["admin", "inactive"] });
*
* @example
* // Combining traits and overrides
* const customAdmin = userBuilder.one({
* traits: ["admin"],
* overrides: { firstName: "Super" }
* });
*/
Export Pattern
// Individual export (preferred)
export const yourTypeBuilder = build<YourType>({...});
// Or grouped export for multiple related builders
export const builders = {
user: userBuilder,
address: addressBuilder,
order: orderBuilder
};
// Helper functions for common scenarios
export const createTestUsers = {
admin: () => userBuilder.one({ traits: ["admin"] }),
guest: () => userBuilder.one({ traits: ["guest"] }),
inactive: () => userBuilder.one({ traits: ["inactive"] }),
withCustomEmail: (email: string) => userBuilder.one({ overrides: { email } }),
list: (count: number) => Array.from({ length: count }, () => userBuilder.one())
};
Integration with Project Patterns
For types that match Firebase patterns (see CLAUDE.md):
*Base type for builder (without id, createdAt, updatedAt)createdAt: perBuild(() => faker.date.past())import { build, sequence, perBuild } from "@jackfranklin/test-data-bot";
import { faker } from "@faker-js/faker/locale/pl";
import type { OnboardingBase, Onboarding } from "~/features/onboarding/types/onboarding";
import { OnboardingSteps } from "~/features/onboarding/types/onboarding";
// Builder for base type (without metadata)
export const onboardingBaseBuilder = build<OnboardingBase>({
fields: {
completed: false,
completedAt: null,
currentStep: OnboardingSteps.PREFERENCES,
products: perBuild(() => productsBuilder.one())
},
traits: {
initial: {
overrides: {
completed: false,
currentStep: OnboardingSteps.WELCOME
}
},
completed: {
overrides: {
completed: true,
completedAt: perBuild(() => faker.date.recent()),
currentStep: OnboardingSteps.CATEGORIES
}
}
}
});
// Builder for application type (with id and timestamps)
export const onboardingBuilder = build<Onboarding>({
fields: {
id: perBuild(() => faker.string.uuid()),
completed: false,
completedAt: null,
currentStep: OnboardingSteps.PREFERENCES,
products: perBuild(() => productsBuilder.one()),
createdAt: perBuild(() => faker.date.past()),
updatedAt: perBuild(() => faker.date.recent())
},
traits: {
initial: {
overrides: {
completed: false,
currentStep: OnboardingSteps.WELCOME
}
},
completed: {
overrides: {
completed: true,
completedAt: perBuild(() => faker.date.recent()),
currentStep: OnboardingSteps.CATEGORIES
}
}
}
});
@jackfranklin/test-data-bot for builders (NOT Fishery)@faker-js/faker/locale/pl for Polish localizationfeatures/*/test/builders/ or tests/builders/sequence() for auto-incremented IDsperBuild(() => ...) for values that should be generated fresh each timeperBuild() wrappertraits for different variants of the same typepostBuild hook for computed fields and transformationsPercentage/Allocation Fields:
allocation: perBuild(() => faker.number.int({ min: 0, max: 100 }));
Currency Amounts:
amount: perBuild(() => faker.number.float({ min: 0, max: 10000, fractionDigits: 2 }));
Icons (from project's design system):
icon: perBuild(() => faker.helpers.arrayElement(["home", "car", "food", "health", "education"]));
Colors (Tailwind classes):
color: perBuild(() => faker.helpers.arrayElement(["red", "blue", "green", "yellow", "purple"]));
Unique Emails (using sequence):
export const userBuilder = build<User>({
fields: {
email: sequence((n) => `user${n}@example.com`)
// ...
}
});
Computed Fields (using postBuild):
export const accountBuilder = build<Account>({
fields: {
id: sequence(),
balance: perBuild(() => faker.number.int({ min: 0, max: 10000 })),
type: "basic",
creditLimit: 0 // Will be computed in postBuild
},
postBuild: (account) => {
// Set credit limit based on account type
switch (account.type) {
case "premium":
account.creditLimit = 5000;
break;
case "vip":
account.creditLimit = 20000;
break;
default:
account.creditLimit = 0;
}
return account;
},
traits: {
premium: {
overrides: { type: "premium" }
},
vip: {
overrides: { type: "vip" }
}
}
});
For each builder creation:
features/*/test/builders/ or tests/builders/)Ask clarifying questions if: