Standards for authoring SDD plugin agents — frontmatter, self-containment, skill references, and no-user-interaction rules.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Unit testing patterns with Vitest - mocking, fixtures, isolation, and fast feedback.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
설치 방법 보기설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.
Fast, isolated tests that verify individual functions and modules. Unit tests run in the CI runner (not Kubernetes) for rapid feedback.
| Aspect | Details |
|---|---|
| Location | components/*/src/**/*.test.ts |
| Framework | Vitest |
| Runs In | CI runner (fast feedback) |
| Written By | Implementors (alongside their code) |
Place test files next to the code they test:
src/model/use-cases/
├── create_user.ts
├── create_user.test.ts # Unit test for create_user
├── update_user.ts
└── update_user.test.ts
// src/model/use-cases/create_user.test.ts
import { describe, it, expect } from 'vitest';
import { createUser } from './create_user';
import type { Dependencies } from '../dependencies';
/**
* @spec changes/user-management/SPEC.md
* @issue PROJ-123
*/
describe('createUser', () => {
describe('AC1: Valid user creation', () => {
it('creates user when email is unique', async () => {
// Arrange (Given)
const mockDeps = createMockDependencies();
const args = { email: 'test@example.com', name: 'Test User' };
// Act (When)
const result = await createUser(mockDeps, args);
// Assert (Then)
expect(result.success).toBe(true);
if (result.success) {
expect(result.user.email).toBe('test@example.com');
}
});
});
});
For functions that receive dependencies as the first argument (CMDO pattern):
import type { Dependencies } from '../dependencies';
const createMockDependencies = (overrides?: Partial<Dependencies>): Dependencies => ({
findUserByEmail: async () => null,
findUserById: async () => null,
insertUser: async (data) => ({ id: 'mock-id', ...data, createdAt: new Date() }),
updateUser: async (id, data) => ({ id, ...data, updatedAt: new Date() }),
...overrides,
});
// Usage in tests
it('returns error when email exists', async () => {
const deps = createMockDependencies({
findUserByEmail: async () => ({ id: '123', email: 'exists@test.com', name: 'Existing' }),
});
const result = await createUser(deps, { email: 'exists@test.com', name: 'New' });
expect(result.success).toBe(false);
});
For tracking calls and controlling return values:
import { describe, it, expect, vi } from 'vitest';
it('calls insertUser with correct data', async () => {
const insertUser = vi.fn().mockResolvedValue({ id: '123', email: 'test@example.com' });
const deps = createMockDependencies({ insertUser });
await createUser(deps, { email: 'test@example.com', name: 'Test' });
expect(insertUser).toHaveBeenCalledOnce();
expect(insertUser).toHaveBeenCalledWith(
expect.objectContaining({ email: 'test@example.com' })
);
});
For mocking entire modules (use sparingly):
import { vi } from 'vitest';
vi.mock('../services/email', () => ({
sendEmail: vi.fn().mockResolvedValue({ sent: true }),
}));
import { sendEmail } from '../services/email';
it('sends welcome email after user creation', async () => {
await createUserWithWelcomeEmail(deps, { email: 'new@test.com' });
expect(sendEmail).toHaveBeenCalledWith(
expect.objectContaining({ to: 'new@test.com', template: 'welcome' })
);
});
Create reusable factories for test data:
// src/__tests__/fixtures/user.ts
import type { User } from '../../types/generated';
export const createTestUser = (overrides?: Partial<User>): User => ({
id: 'test-user-id',
email: 'test@example.com',
name: 'Test User',
role: 'planner',
clientId: 'test-client',
createdAt: new Date('2026-01-01'),
updatedAt: new Date('2026-01-01'),
...overrides,
});
// Usage
it('formats user display name', () => {
const user = createTestUser({ name: 'John Doe' });
expect(formatDisplayName(user)).toBe('John Doe');
});
it('uses email when name is empty', () => {
const user = createTestUser({ name: '', email: 'john@example.com' });
expect(formatDisplayName(user)).toBe('john');
});
// src/__tests__/fixtures/constants.ts
export const TEST_USER_ID = 'test-user-123';
export const TEST_CLIENT_ID = 'test-client-456';
export const TEST_EMAIL = 'test@example.com';
export const VALID_CREDENTIALS = {
email: TEST_EMAIL,
password: 'ValidPass123!',
};
export const INVALID_CREDENTIALS = {
email: TEST_EMAIL,
password: 'wrong',
};
import { describe, it, beforeEach, afterEach, vi } from 'vitest';
describe('userService', () => {
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.restoreAllMocks();
});
// Tests run in isolation
});
// BAD: Shared mutable state
let testUser = { id: '1', name: 'Test' };
it('test 1', () => {
testUser.name = 'Modified'; // Affects other tests!
});
// GOOD: Create fresh data per test
it('test 1', () => {
const testUser = createTestUser();
// Modifications don't affect other tests
});
import { vi, beforeEach, afterEach } from 'vitest';
describe('time-dependent tests', () => {
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-01-15T10:00:00Z'));
});
afterEach(() => {
vi.useRealTimers();
});
it('calculates expiration correctly', () => {
const token = createToken({ expiresIn: '1h' });
expect(token.expiresAt).toEqual(new Date('2026-01-15T11:00:00Z'));
});
});
it('handles async operations', async () => {
const result = await createUser(deps, validArgs);
expect(result.success).toBe(true);
});
it('handles rejected promises', async () => {
const deps = createMockDependencies({
insertUser: async () => { throw new Error('DB connection failed'); },
});
await expect(createUser(deps, validArgs)).rejects.toThrow('DB connection failed');
});
it('returns error result for validation failure', async () => {
const result = await createUser(deps, { email: 'invalid-email', name: '' });
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error).toBe('invalid_email');
}
});
it('handles database errors gracefully', async () => {
const deps = createMockDependencies({
insertUser: async () => { throw new Error('Connection timeout'); },
});
const result = await createUser(deps, validArgs);
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error).toBe('database_error');
}
});
For functions returning discriminated union results:
type CreateUserResult =
| { readonly success: true; readonly user: User }
| { readonly success: false; readonly error: 'email_exists' | 'invalid_email' };
it('returns success with user data', async () => {
const result = await createUser(deps, validArgs);
// Type narrowing via discriminant
expect(result.success).toBe(true);
if (result.success) {
expect(result.user.email).toBe('test@example.com');
expect(result.user.id).toBeDefined();
}
});
it('returns specific error code', async () => {
const deps = createMockDependencies({
findUserByEmail: async () => existingUser,
});
const result = await createUser(deps, { email: existingUser.email, name: 'New' });
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error).toBe('email_exists');
}
});
| Metric | Target |
|---|---|
| Line coverage | ≥80% |
| Branch coverage | ≥75% |
| Function coverage | ≥90% |
create_user.ts → create_user.test.ts@spec and @issue JSDoc tagsBefore committing unit tests, verify:
.test.ts suffix@spec and @issue tags present in file headerThis skill defines no input parameters or structured output.
아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.
로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.
아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.
Standards for authoring SDD plugin agents — frontmatter, self-containment, skill references, and no-user-interaction rules.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Scaffolds Node.js/TypeScript backend components with CMDO architecture, driven by component settings.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
CMDO architecture standards for Node.js/TypeScript backends with strict layer separation.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Intent-to-command mappings for fullstack-typescript tech pack features.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Create change specification and implementation plan with dynamic phase generation. Supports feature, bugfix, refactor, and epic types.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.
Orchestrates the full change lifecycle — routes actions to phase-specific sub-files.
한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.