テストコードで毎回const user = { id: 'test-user', name: 'テスト太郎', email: 'test@example.com', ... }と書いていませんか?Claude Codeを使ってFactory・Faker・Prismaを統合したテストフィクスチャを設計すると、テストデータの準備が劇的にシンプルになります。本記事では、現場で使えるパターンを体系的に紹介します。
CLAUDE.mdルール
プロジェクトのCLAUDE.mdに以下を記載することで、Claude Codeがテスト設計時に自動的にFactoryパターンを使ったコードを生成します。
## テスト設計ルール
- テストデータはFactoryパターンで生成する(オブジェクトリテラルの直書き禁止)
- Factory.build()はDB接続なしのユニットテスト用、Factory.create()はDB接続ありの結合テスト用
- Faker.jsは日本語ロケール(ja)を使用し、本番に近いリアルなデータを生成する
- 関連エンティティは自動生成(OrderFactoryはUserとProductを自動create)
- createTestWorld()ヘルパーで複合シナリオのデータセットアップを共通化する
Factory.build() vs Factory.create() の使い分け
Factoryの基本パターンから始めます。build()はメモリ内オブジェクト、create()はDB保存まで行います。
// tests/factories/user.factory.ts
import { faker } from '@faker-js/faker/locale/ja';
import { prisma } from '../../src/lib/prisma';
import type { User } from '@prisma/client';
type UserOverride = Partial<Omit<User, 'id' | 'createdAt' | 'updatedAt'>>;
export const UserFactory = {
// DB接続なし — ユニットテストで高速に使える
build: (overrides: UserOverride = {}): Omit<User, 'id' | 'createdAt' | 'updatedAt'> => ({
name: faker.person.fullName(),
email: faker.internet.email(),
role: 'USER',
isActive: true,
...overrides,
}),
// DB保存あり — 結合テスト・E2Eテストで使う
create: async (overrides: UserOverride = {}): Promise<User> => {
return prisma.user.create({
data: UserFactory.build(overrides),
});
},
// 複数件一括生成
createMany: async (count: number, overrides: UserOverride = {}): Promise<User[]> => {
return Promise.all(
Array.from({ length: count }, () => UserFactory.create(overrides))
);
},
};
ユニットテストではbuild()を使うことでDB不要・高速実行が可能になります。
// tests/unit/user.service.test.ts
import { UserFactory } from '../factories/user.factory';
describe('UserService.validateAge', () => {
it('未成年ユーザーを弾く', () => {
const user = UserFactory.build({ birthYear: 2015 }); // DB接続なし
expect(validateAge(user)).toBe(false);
});
});
Faker.jsの日本語ロケール活用
Faker.jsのjaロケールを使うと、本番データに近いリアルなテストデータが生成できます。
// tests/factories/product.factory.ts
import { faker } from '@faker-js/faker/locale/ja';
import { prisma } from '../../src/lib/prisma';
import type { Product } from '@prisma/client';
type ProductOverride = Partial<Omit<Product, 'id' | 'createdAt' | 'updatedAt'>>;
export const ProductFactory = {
build: (overrides: ProductOverride = {}) => ({
name: faker.commerce.productName(),
description: faker.commerce.productDescription(),
price: Number(faker.commerce.price({ min: 100, max: 50000 })),
category: faker.helpers.arrayElement(['電子機器', '書籍', '食品', '衣料品']),
stock: faker.number.int({ min: 0, max: 999 }),
sku: faker.string.alphanumeric(8).toUpperCase(),
isPublished: true,
...overrides,
}),
create: async (overrides: ProductOverride = {}): Promise<Product> => {
return prisma.product.create({
data: ProductFactory.build(overrides),
});
},
};
// 住所・電話番号なども日本語で生成できる
// faker.location.city() → 例: 「大阪市」
// faker.phone.number() → 例: 「090-1234-5678」
// faker.company.name() → 例: 「株式会社山田商事」
OrderFactoryで関連エンティティを自動生成
注文(Order)を作るにはUserとProductが必要ですが、OrderFactoryが自動的に関連エンティティを生成します。
// tests/factories/order.factory.ts
import { faker } from '@faker-js/faker/locale/ja';
import { prisma } from '../../src/lib/prisma';
import { UserFactory } from './user.factory';
import { ProductFactory } from './product.factory';
import type { Order, User, Product } from '@prisma/client';
type OrderWithRelations = Order & { user: User; product: Product };
type OrderOverride = {
userId?: string;
productId?: string;
quantity?: number;
status?: Order['status'];
};
export const OrderFactory = {
create: async (overrides: OrderOverride = {}): Promise<OrderWithRelations> => {
// UserとProductが未指定なら自動生成
const user = overrides.userId
? await prisma.user.findUniqueOrThrow({ where: { id: overrides.userId } })
: await UserFactory.create();
const product = overrides.productId
? await prisma.product.findUniqueOrThrow({ where: { id: overrides.productId } })
: await ProductFactory.create();
const quantity = overrides.quantity ?? faker.number.int({ min: 1, max: 10 });
const order = await prisma.order.create({
data: {
userId: user.id,
productId: product.id,
quantity,
totalPrice: product.price * quantity,
status: overrides.status ?? 'PENDING',
},
include: { user: true, product: true },
});
return order;
},
};
// テストではOrderFactory.create()一発でUser+Product+Order全て揃う
// const order = await OrderFactory.create({ status: 'COMPLETED' });
// order.user.name → 自動生成されたユーザー名
// order.product.name → 自動生成された商品名
createTestWorld() — 複合シナリオのデータセットアップ
複数のテストで同じデータセットが必要な場合は、createTestWorld()ヘルパーで共通化します。
// tests/helpers/test-world.ts
import { UserFactory } from '../factories/user.factory';
import { ProductFactory } from '../factories/product.factory';
import { OrderFactory } from '../factories/order.factory';
import type { User, Product, Order } from '@prisma/client';
interface TestWorld {
adminUser: User;
regularUser: User;
premiumProduct: Product;
standardProduct: Product;
pendingOrder: Order & { user: User; product: Product };
completedOrder: Order & { user: User; product: Product };
}
export async function createTestWorld(): Promise<TestWorld> {
const [adminUser, regularUser] = await Promise.all([
UserFactory.create({ role: 'ADMIN' }),
UserFactory.create({ role: 'USER' }),
]);
const [premiumProduct, standardProduct] = await Promise.all([
ProductFactory.create({ price: 9800, category: '電子機器' }),
ProductFactory.create({ price: 980, category: '書籍' }),
]);
const [pendingOrder, completedOrder] = await Promise.all([
OrderFactory.create({ userId: regularUser.id, productId: standardProduct.id, status: 'PENDING' }),
OrderFactory.create({ userId: regularUser.id, productId: premiumProduct.id, status: 'COMPLETED' }),
]);
return { adminUser, regularUser, premiumProduct, standardProduct, pendingOrder, completedOrder };
}
// テストでの使用例
// describe('OrderService', () => {
// let world: TestWorld;
// beforeEach(async () => { world = await createTestWorld(); });
//
// it('管理者は全注文を取得できる', async () => {
// const orders = await orderService.findAll(world.adminUser.id);
// expect(orders.length).toBeGreaterThanOrEqual(2);
// });
// });
まとめ
- Factory.build() / create() を使い分けることで、ユニットテストはDB不要・高速、結合テストはリアルなデータで検証できる
-
Faker.jsの
jaロケールにより、日本語の氏名・住所・商品名などが自動生成でき、本番に近いテストシナリオが構築できる - OrderFactoryの関連エンティティ自動生成で、テスト記述のボイラープレートが大幅に削減され、テストの意図が明確になる
-
createTestWorld()ヘルパーで複合シナリオを共通化すると、
beforeEachの肥大化を防ぎ、テストの可読性と保守性が向上する
さらに深く学ぶ
この記事で紹介したFactory・Prismaテストパターンを含む、Claude Codeのコードレビュー・設計支援プロンプトをまとめたCode Review Pack(¥980)をnoteで販売中です。
テスト設計・型安全・セキュリティレビューなど、現場で即使えるプロンプト集です。ぜひご活用ください。
Top comments (0)