关于 test-data-factory-builder
用于设计可复用测试数据工厂和构建器的 TypeScript 技能,涵盖关联数据、可选数据库持久化、特征变体、序列生成、数据生成器和测试辅助工具。
主要功能
- 01可组合的工厂与构建器模式
- 02创建关联记录
- 03可选数据库持久化
- 04特征变体与序列生成
- 05逼真的数据生成器
- 06测试数据库辅助工具
适用场景
- 01创建一致的测试对象
- 02构建关联的用户、产品和订单
- 03使用可复用工具填充测试数据库
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ patricio0312rev/ skills --skill test-data-factory-builder安装后在 Claude Code 中输入/test-data-factory-builder确认已加载。
详细介绍
测试数据工厂构建器
创建可组合的工厂,以生成一致的测试数据。
工厂模式
// factories/UserFactory.ts
import { faker } from "@faker-js/faker";
export class UserFactory {
private data: Partial<User> = {};
static create(overrides?: Partial<User>): User {
return new UserFactory().with(overrides).build();
}
with(overrides: Partial<User>): this {
this.data = { ...this.data, ...overrides };
return this;
}
withEmail(email: string): this {
this.data.email = email;
return this;
}
withRole(role: UserRole): this {
this.data.role = role;
return this;
}
asAdmin(): this {
this.data.role = "ADMIN";
return this;
}
build(): User {
return {
id: this.data.id || faker.string.uuid(),
email: this.data.email || faker.internet.email(),
name: this.data.name || faker.person.fullName(),
role: this.data.role || "USER",
createdAt: this.data.createdAt || faker.date.past(),
...this.data,
};
}
}
// Usage
const user = UserFactory.create();
const admin = UserFactory.create().asAdmin().build();
const specific = UserFactory.create({ email: "[email protected]" });
构建器模式
// builders/OrderBuilder.ts
export class OrderBuilder {
private user?: User;
private items: OrderItem[] = [];
private status: OrderStatus = "PENDING";
forUser(user: User): this {
this.user = user;
return this;
}
withItem(product: Product, quantity: number = 1): this {
this.items.push({
id: faker.string.uuid(),
productId: product.id,
quantity,
price: product.price,
});
return this;
}
withStatus(status: OrderStatus): this {
this.status = status;
return this;
}
asPaid(): this {
this.status = "PAID";
return this;
}
async build(): Promise<Order> {
if (!this.user) {
throw new Error("User is required");
}
const total = this.items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
return {
id: faker.string.uuid(),
userId: this.user.id,
items: this.items,
total,
status: this.status,
createdAt: new Date(),
};
}
}
// Usage
const order = await new OrderBuilder()
.forUser(user)
.withItem(laptop, 2)
.withItem(phone, 1)
.asPaid()
.build();
关系处理
// factories/OrderFactory.ts
export class OrderFactory {
static async createWithUser(overrides?: Partial<Order>): Promise<Order> {
// Create user if not provided
const user = UserFactory.create();
// Create products
const products = [
ProductFactory.create({ price: 99.99 }),
ProductFactory.create({ price: 199.99 }),
];
// Create order with relationships
return {
id: faker.string.uuid(),
userId: user.id,
user,
items: products.map((product) => ({
id: faker.string.uuid(),
productId: product.id,
product,
quantity: 1,
price: product.price,
})),
total: products.reduce((sum, p) => sum + p.price, 0),
status: "PENDING",
createdAt: new Date(),
...overrides,
};
}
}
数据库持久化
// factories/UserFactory.ts with persistence
export class UserFactory {
private prisma: PrismaClient;
constructor(prisma: PrismaClient) {
this.prisma = prisma;
}
async create(overrides?: Partial<User>): Promise<User> {
const data = {
email: faker.internet.email(),
name: faker.person.fullName(),
role: "USER",
...overrides,
};
return this.prisma.user.create({ data });
}
async createMany(count: number): Promise<User[]> {
return Promise.all(Array.from({ length: count }, () => this.create()));
}
async createAdmin(): Promise<User> {
return this.create({ role: "ADMIN" });
}
}
// Usage in tests
test("should list users", async () => {
const userFactory = new UserFactory(prisma);
await userFactory.createMany(5);
const users = await userService.list();
expect(users).toHaveLength(5);
});
特征模式
// factories/UserFactory.ts with traits
export class UserFactory {
private traits: string[] = [];
withTrait(trait: string): this {
this.traits.push(trait);
return this;
}
build(): User {
let user: User = {
id: faker.string.uuid(),
email: faker.internet.email(),
name: faker.person.fullName(),
role: "USER",
createdAt: new Date(),
};
// Apply traits
if (this.traits.includes("verified")) {
user.emailVerified = true;
user.verifiedAt = new Date();
}
if (this.traits.includes("suspended")) {
user.status = "SUSPENDED";
user.suspendedAt = new Date();
}
if (this.traits.includes("premium")) {
user.subscription = "PREMIUM";
user.subscriptionExpiresAt = faker.date.future();
}
return user;
}
}
// Usage
const verifiedUser = new UserFactory().withTrait("verified").build();
const suspendedPremiumUser = new UserFactory()
.withTrait("suspended")
.withTrait("premium")
.build();
序列生成
// factories/sequence.ts
class Sequence {
private counters = new Map<string, number>();
next(key: string): number {
const current = this.counters.get(key) || 0;
const next = current + 1;
this.counters.set(key, next);
return next;
}
reset(key?: string): void {
if (key) {
this.counters.delete(key);
} else {
this.counters.clear();
}
}
}
const sequence = new Sequence();
// Usage in factory
export class UserFactory {
build(): User {
return {
id: faker.string.uuid(),
email: `user${sequence.next("user")}@example.com`,
name: `Test User ${sequence.next("user")}`,
// ...
};
}
}
// Creates: [email protected], [email protected], etc.
可组合工厂
// factories/index.ts
export const TestDataBuilder = {
user: (overrides?: Partial<User>) => new UserFactory().with(overrides),
product: (overrides?: Partial<Product>) =>
new ProductFactory().with(overrides),
order: () => new OrderBuilder(),
// Composite builders
checkoutScenario: async () => {
const user = TestDataBuilder.user().build();
const products = [
TestDataBuilder.product({ price: 99.99 }).build(),
TestDataBuilder.product({ price: 199.99 }).build(),
];
const order = await TestDataBuilder.order()
.forUser(user)
.withItem(products[0], 2)
.withItem(products[1], 1)
.build();
return { user, products, order };
},
};
// Usage
test("should process checkout", async () => {
const { user, order } = await TestDataBuilder.checkoutScenario();
const result = await checkoutService.process(order);
expect(result.status).toBe("SUCCESS");
});
逼真的数据生成器
// generators/realistic.ts
import { faker } from "@faker-js/faker";
export const RealisticData = {
creditCard: () => ({
number: "4242424242424242", // Test card
expiry: faker.date.future().toISOString().slice(0, 7), // YYYY-MM
cvc: "123",
name: faker.person.fullName(),
}),
address: () => ({
street: faker.location.streetAddress(),
city: faker.location.city(),
state: faker.location.state(),
zip: faker.location.zipCode(),
country: "US",
}),
product: () => ({
name: faker.commerce.productName(),
description: faker.commerce.productDescription(),
price: parseFloat(faker.commerce.price()),
category: faker.commerce.department(),
sku: faker.string.alphanumeric(10).toUpperCase(),
}),
email: {
valid: () => faker.internet.email(),
invalid: () => "invalid-email",
disposable: () => `${faker.string.alphanumeric(8)}@tempmail.com`,
},
};
工厂注册表
// factories/registry.ts
class FactoryRegistry {
private factories = new Map();
register<T>(name: string, factory: () => T): void {
this.factories.set(name, factory);
}
create<T>(name: string, overrides?: Partial<T>): T {
const factory = this.factories.get(name);
if (!factory) {
throw new Error(`Factory not found: ${name}`);
}
const instance = factory();
return { ...instance, ...overrides };
}
}
const registry = new FactoryRegistry();
// Register factories
registry.register("user", () => UserFactory.create());
registry.register("product", () => ProductFactory.create());
// Usage
const user = registry.create("user", { role: "ADMIN" });
测试辅助工具
// helpers/test-data.ts
export async function seedTestDatabase(prisma: PrismaClient) {
const userFactory = new UserFactory(prisma);
const productFactory = new ProductFactory(prisma);
// Create base data
const users = await userFactory.createMany(10);
const products = await productFactory.createMany(20);
// Create relationships
for (const user of users.slice(0, 5)) {
await new OrderBuilder()
.forUser(user)
.withItem(products[0], 2)
.withItem(products[1], 1)
.asPaid()
.build();
}
return { users, products };
}
// Usage
beforeEach(async () => {
await seedTestDatabase(prisma);
});
最佳实践
- 默认保持确定性:使用带种子的 faker
- 最少数据:只创建所需数据
- 可组合:组合工厂
- 类型安全:完整支持 TypeScript
- 关系:轻松创建相关数据
- 与数据库无关:可在有或没有数据库的情况下使用
- 命名清晰:使用描述性工厂方法
输出检查清单
- 已创建工厂类
- 已实现构建器模式
- 已处理关系
- 数据库持久化选项
- 用于不同变体的特征
- 序列生成
- 可组合构建器
- 逼真的数据生成器
- 工厂注册表(可选)
- 已创建测试辅助工具
评论
支持 Markdown