ワンクリックで
testing
Testing patterns for behavior-driven tests. Use when writing tests or test factories.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Testing patterns for behavior-driven tests. Use when writing tests or test factories.
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
Apply template-downstream to all registered downstream projects in bulk, one project at a time. Use when you want to push the latest .claude/, .mcp.json, or .pre-commit-config.yaml to every project at once. Triggers on: "일괄 배포", "전체 프로젝트에 내려보내기", "모든 프로젝트 업데이트", "broadcast template", "bulk sync".
The standard business voice for any Korean text a human reads — a formal-yet-friendly business register built on 해요체 but elevated with 합니다/습니다 endings, polite recommendation phrasing, natural greetings/closings, and readable structure. Use whenever producing or rewriting reader-facing text: Telegram replies, reports, READMEs, job outputs, human-facing notes, and especially when rewriting raw TIL/technical notes for sharing with colleagues. Triggers: 비즈니스 톤, business voice, 격식 있는 톤, 동료 공유용 재작성, formal rewrite, 보고서 톤, 발표자료 톤.
한국 특화 기능(교통·날씨·부동산·쇼핑·법률·금융·공공데이터 등) 요청이 들어오면 실행. git pull로 최신화 후 README 레지스트리에서 스킬을 골라 해당 SKILL.md만 읽어 진행한다.
Import Dev Sentinel experiences into localdocs/learn.sentinel.md. Use when the user wants to bring gotchas, failure lessons, or struggle experiences captured by Dev Sentinel into the project's local knowledge base. Triggers: "sentinel 가져와", "sentinel import", "gotcha 정리", "경험 가져와", "sentinel에서 배운 것", "실패 경험 가져와".
Update .claude/, .mcp.json, and .pre-commit-config.yaml from the upstream python-project-template repository into the current project. Only files that exist upstream are updated — project-only files are never deleted. Use when pulling latest tooling, rules, hooks, or skills from the canonical template into this project. Triggers on: "템플릿 최신화", "template sync", "upstream 반영", "template 업데이트".
Planning work in small, known-good increments. Use when starting significant work or breaking down complex tasks.
| name | testing |
| description | Testing patterns for behavior-driven tests. Use when writing tests or test factories. |
Test behavior, not implementation. 100% coverage through business behavior, not implementation details.
Example: Validation code in payment-validator.ts gets 100% coverage by testing processPayment() behavior, NOT by directly testing validator functions.
Never test implementation details. Test behavior through public APIs.
Why this matters:
❌ WRONG - Testing implementation:
// ❌ Testing HOW (implementation detail)
it('should call validateAmount', () => {
const spy = jest.spyOn(validator, 'validateAmount');
processPayment(payment);
expect(spy).toHaveBeenCalled(); // Tests HOW, not WHAT
});
// ❌ Testing private methods
it('should validate CVV format', () => {
const result = validator._validateCVV('123'); // Private method!
expect(result).toBe(true);
});
// ❌ Testing internal state
it('should set isValidated flag', () => {
processPayment(payment);
expect(processor.isValidated).toBe(true); // Internal state
});
✅ CORRECT - Testing behavior through public API:
it('should reject negative amounts', () => {
const payment = getMockPayment({ amount: -100 });
const result = processPayment(payment);
expect(result.success).toBe(false);
expect(result.error).toContain('Amount must be positive');
});
it('should reject invalid CVV', () => {
const payment = getMockPayment({ cvv: '12' }); // Only 2 digits
const result = processPayment(payment);
expect(result.success).toBe(false);
expect(result.error).toContain('Invalid CVV');
});
it('should process valid payments', () => {
const payment = getMockPayment({ amount: 100, cvv: '123' });
const result = processPayment(payment);
expect(result.success).toBe(true);
expect(result.data.transactionId).toBeDefined();
});
Validation code gets 100% coverage by testing the behavior it protects:
// Tests covering validation WITHOUT testing validator directly
describe('processPayment', () => {
it('should reject negative amounts', () => {
const payment = getMockPayment({ amount: -100 });
const result = processPayment(payment);
expect(result.success).toBe(false);
});
it('should reject amounts over 10000', () => {
const payment = getMockPayment({ amount: 15000 });
const result = processPayment(payment);
expect(result.success).toBe(false);
});
it('should reject invalid CVV', () => {
const payment = getMockPayment({ cvv: '12' });
const result = processPayment(payment);
expect(result.success).toBe(false);
});
it('should process valid payments', () => {
const payment = getMockPayment({ amount: 100, cvv: '123' });
const result = processPayment(payment);
expect(result.success).toBe(true);
});
});
// ✅ Result: payment-validator.ts has 100% coverage through behavior
Key insight: When coverage drops, ask "What business behavior am I not testing?" not "What line am I missing?"
For test data, use factory functions with optional overrides.
Partial<T> overrides for customizationlet/beforeEach - use factories for fresh stateconst getMockUser = (overrides?: Partial<User>): User => {
return UserSchema.parse({
id: 'user-123',
name: 'Test User',
email: 'test@example.com',
role: 'user',
...overrides,
});
};
// Usage
it('creates user with custom email', () => {
const user = getMockUser({ email: 'custom@example.com' });
const result = createUser(user);
expect(result.success).toBe(true);
});
import { UserSchema } from '@/schemas'; // Import real schema
const getMockUser = (overrides?: Partial<User>): User => {
return UserSchema.parse({
id: 'user-123',
name: 'Test User',
email: 'test@example.com',
role: 'user',
isActive: true,
createdAt: new Date('2024-01-01'),
...overrides,
});
};
Why validate with schema?
For nested objects, compose factories:
const getMockItem = (overrides?: Partial<Item>): Item => {
return ItemSchema.parse({
id: 'item-1',
name: 'Test Item',
price: 100,
...overrides,
});
};
const getMockOrder = (overrides?: Partial<Order>): Order => {
return OrderSchema.parse({
id: 'order-1',
items: [getMockItem()], // ✅ Compose factories
customer: getMockCustomer(), // ✅ Compose factories
payment: getMockPayment(), // ✅ Compose factories
...overrides,
});
};
// Usage - override nested objects
it('calculates total with multiple items', () => {
const order = getMockOrder({
items: [
getMockItem({ price: 100 }),
getMockItem({ price: 200 }),
],
});
expect(calculateTotal(order)).toBe(300);
});
❌ WRONG: Using let and beforeEach
let user: User;
beforeEach(() => {
user = { id: 'user-123', name: 'Test User', ... }; // Shared mutable state!
});
it('test 1', () => {
user.name = 'Modified User'; // Mutates shared state
});
it('test 2', () => {
expect(user.name).toBe('Test User'); // Fails! Modified by test 1
});
✅ CORRECT: Factory per test
it('test 1', () => {
const user = getMockUser({ name: 'Modified User' }); // Fresh state
// ...
});
it('test 2', () => {
const user = getMockUser(); // Fresh state, not affected by test 1
expect(user.name).toBe('Test User'); // ✅ Passes
});
❌ WRONG: Incomplete objects
const getMockUser = () => ({
id: 'user-123', // Missing name, email, role!
});
✅ CORRECT: Complete objects
const getMockUser = (overrides?: Partial<User>): User => {
return UserSchema.parse({
id: 'user-123',
name: 'Test User',
email: 'test@example.com',
role: 'user',
...overrides, // All required fields present
});
};
❌ WRONG: Redefining schemas in tests
// ❌ Schema already defined in src/schemas/user.ts!
const UserSchema = z.object({ ... });
const getMockUser = () => UserSchema.parse({ ... });
✅ CORRECT: Import real schema
import { UserSchema } from '@/schemas/user';
const getMockUser = (overrides?: Partial<User>): User => {
return UserSchema.parse({
id: 'user-123',
name: 'Test User',
email: 'test@example.com',
...overrides,
});
};
Watch for these patterns that give fake 100% coverage:
❌ WRONG - Gives 100% coverage but tests nothing:
it('calls validator', () => {
const spy = jest.spyOn(validator, 'validate');
validate(payment);
expect(spy).toHaveBeenCalled(); // Meaningless assertion
});
✅ CORRECT - Test actual behavior:
it('should reject invalid payment', () => {
const payment = getMockPayment({ amount: -100 });
const result = validate(payment);
expect(result.success).toBe(false);
expect(result.error).toContain('Amount must be positive');
});
❌ WRONG - No behavior validation:
it('processes payment', () => {
const spy = jest.spyOn(processor, 'process');
handlePayment(payment);
expect(spy).toHaveBeenCalledWith(payment); // So what?
});
✅ CORRECT - Verify the outcome:
it('should process payment and return transaction ID', () => {
const payment = getMockPayment();
const result = handlePayment(payment);
expect(result.success).toBe(true);
expect(result.transactionId).toBeDefined();
});
❌ WRONG - Testing implementation, not behavior:
it('sets amount', () => {
payment.setAmount(100);
expect(payment.getAmount()).toBe(100); // Trivial
});
✅ CORRECT - Test meaningful behavior:
it('should calculate total with tax', () => {
const order = createOrder({ items: [item1, item2] });
const total = order.calculateTotal();
expect(total).toBe(230); // 200 + 15% tax
});
❌ WRONG - Missing edge cases:
it('validates payment', () => {
const result = validate(getMockPayment());
expect(result.success).toBe(true); // Only happy path!
});
// Missing: negative amounts, invalid CVV, missing fields, etc.
✅ CORRECT - Test all branches:
describe('validate payment', () => {
it('should reject negative amounts', () => {
const payment = getMockPayment({ amount: -100 });
expect(validate(payment).success).toBe(false);
});
it('should reject amounts over limit', () => {
const payment = getMockPayment({ amount: 15000 });
expect(validate(payment).success).toBe(false);
});
it('should reject invalid CVV', () => {
const payment = getMockPayment({ cvv: '12' });
expect(validate(payment).success).toBe(false);
});
it('should accept valid payments', () => {
const payment = getMockPayment();
expect(validate(payment).success).toBe(true);
});
});
Don't create test files that mirror implementation files.
❌ WRONG:
src/
payment-validator.ts
payment-processor.ts
payment-formatter.ts
tests/
payment-validator.test.ts ← 1:1 mapping
payment-processor.test.ts ← 1:1 mapping
payment-formatter.test.ts ← 1:1 mapping
✅ CORRECT:
src/
payment-validator.ts
payment-processor.ts
payment-formatter.ts
tests/
process-payment.test.ts ← Tests behavior, not implementation files
Why: Implementation details can be refactored without changing tests. Tests verify behavior remains correct regardless of how code is organized internally.
When writing tests, verify:
let/beforeEach - use factories for fresh state