Write and execute unit tests that validate individual functions, methods, and components in isolation.
/plugin marketplace add marcel-Ngan/ai-dev-team/plugin install marcel-ngan-ai-dev-team@marcel-Ngan/ai-dev-teamThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Write and execute unit tests that validate individual functions, methods, and components in isolation.
In our TDD workflow, Senior Developer writes unit tests BEFORE implementation (Red phase).
| Phase | Action | Test Status |
|---|---|---|
| Red | Senior Dev writes unit tests | Tests FAIL (no code yet) |
| Green | Junior Dev implements code | Tests PASS |
| Refactor | Clean up while tests stay green | Tests PASS |
| Principle | Meaning | Implementation |
|---|---|---|
| Fast | Tests run quickly | No I/O, mock dependencies |
| Independent | No test dependencies | Each test isolated |
| Repeatable | Same result every time | No random, no time-dep |
| Self-validating | Clear pass/fail | No manual inspection |
| Timely | Written with code | TDD or alongside |
Unit tests should:
describe('Calculator', () => {
describe('add', () => {
it('should return sum of two positive numbers', () => {
// Arrange
const calc = new Calculator();
const a = 5;
const b = 3;
// Act
const result = calc.add(a, b);
// Assert
expect(result).toBe(8);
});
});
});
[Unit].[Method].[Scenario].[ExpectedBehavior]
Examples:
- Calculator.add.positiveNumbers.returnsSum
- UserService.create.duplicateEmail.throwsError
- AuthMiddleware.validate.expiredToken.returns401
describe('{{functionName}}', () => {
// Setup shared across tests
let dependency: MockType;
beforeEach(() => {
dependency = createMock<DependencyType>();
});
afterEach(() => {
jest.clearAllMocks();
});
describe('when {{normalCondition}}', () => {
it('should {{expectedBehavior}}', () => {
// Arrange
const input = {{validInput}};
dependency.method.mockReturnValue({{mockValue}});
// Act
const result = {{functionName}}(input);
// Assert
expect(result).toEqual({{expected}});
});
});
describe('when {{errorCondition}}', () => {
it('should throw {{ErrorType}}', () => {
// Arrange
const invalidInput = {{invalidInput}};
// Act & Assert
expect(() => {{functionName}}(invalidInput))
.toThrow({{ErrorType}});
});
});
describe('edge cases', () => {
it.each([
[{{input1}}, {{expected1}}],
[{{input2}}, {{expected2}}],
[{{input3}}, {{expected3}}],
])('should return %s when input is %s', (input, expected) => {
expect({{functionName}}(input)).toBe(expected);
});
});
});
describe('{{ClassName}}', () => {
let instance: {{ClassName}};
let mockDep1: jest.Mocked<Dep1>;
let mockDep2: jest.Mocked<Dep2>;
beforeEach(() => {
mockDep1 = createMock<Dep1>();
mockDep2 = createMock<Dep2>();
instance = new {{ClassName}}(mockDep1, mockDep2);
});
describe('constructor', () => {
it('should initialize with dependencies', () => {
expect(instance).toBeDefined();
});
});
describe('{{methodName}}', () => {
// Method-specific tests
});
});
describe('{{asyncFunction}}', () => {
it('should resolve with {{expected}} when {{condition}}', async () => {
// Arrange
const input = {{input}};
mockService.fetch.mockResolvedValue({{mockData}});
// Act
const result = await {{asyncFunction}}(input);
// Assert
expect(result).toEqual({{expected}});
});
it('should reject with {{ErrorType}} when {{errorCondition}}', async () => {
// Arrange
mockService.fetch.mockRejectedValue(new Error('{{errorMessage}}'));
// Act & Assert
await expect({{asyncFunction}}({{input}}))
.rejects.toThrow({{ErrorType}});
});
});
// Manual mock
const mockUserRepo = {
findById: jest.fn(),
save: jest.fn(),
delete: jest.fn(),
};
// Type-safe mock
import { mock, MockProxy } from 'jest-mock-extended';
const mockUserRepo: MockProxy<UserRepository> = mock<UserRepository>();
// Mock implementation
mockUserRepo.findById.mockImplementation((id) => {
if (id === 'valid-id') return Promise.resolve(mockUser);
return Promise.resolve(null);
});
// Mock entire module
jest.mock('./userService', () => ({
UserService: jest.fn().mockImplementation(() => ({
getUser: jest.fn().mockResolvedValue(mockUser),
})),
}));
// Partial mock
jest.mock('./utils', () => ({
...jest.requireActual('./utils'),
fetchData: jest.fn(),
}));
describe('time-dependent function', () => {
beforeEach(() => {
jest.useFakeTimers();
jest.setSystemTime(new Date('2025-01-15'));
});
afterEach(() => {
jest.useRealTimers();
});
it('should use current date', () => {
const result = getFormattedDate();
expect(result).toBe('2025-01-15');
});
});
| Type | Target | Critical Path |
|---|---|---|
| Line | 80% | 95% |
| Branch | 70% | 90% |
| Function | 90% | 100% |
| Statement | 80% | 95% |
### High Priority (Must Test)
- Business logic functions
- Data transformations
- Validation logic
- Error handling paths
- Public API methods
### Medium Priority (Should Test)
- Helper/utility functions
- Edge cases
- Boundary conditions
### Low Priority (Nice to Have)
- Simple getters/setters
- Configuration constants
- Type guards
/* istanbul ignore next */
function debugOnly() {
// Only used in development
}
// In jest.config.js
coveragePathIgnorePatterns: [
'/node_modules/',
'/tests/',
'/__mocks__/',
'/types/',
'.d.ts$'
]
src/
services/
userService.ts
userService.test.ts # Co-located tests
tests/
unit/
services/
userService.test.ts # Separate tests folder
integration/
e2e/
describe('UserService', () => {
describe('CRUD operations', () => {
describe('create', () => {
it('should create user with valid data', () => {});
it('should throw on duplicate email', () => {});
});
describe('read', () => {
it('should find user by id', () => {});
it('should return null for non-existent id', () => {});
});
});
describe('authentication', () => {
describe('login', () => {});
describe('logout', () => {});
});
});
// Synchronous
expect(() => validateInput(null)).toThrow(ValidationError);
expect(() => validateInput(null)).toThrow('Input is required');
// Asynchronous
await expect(asyncValidate(null)).rejects.toThrow(ValidationError);
await expect(asyncValidate(null)).rejects.toMatchObject({
code: 'VALIDATION_ERROR',
message: 'Input is required',
});
it('should call callback with result', () => {
const callback = jest.fn();
processData(data, callback);
expect(callback).toHaveBeenCalledTimes(1);
expect(callback).toHaveBeenCalledWith(null, expectedResult);
});
it('should emit event on success', () => {
const handler = jest.fn();
emitter.on('success', handler);
emitter.process();
expect(handler).toHaveBeenCalledWith({ status: 'complete' });
});
describe.each([
{ input: '', expected: false, description: 'empty string' },
{ input: 'a', expected: true, description: 'single char' },
{ input: 'abc', expected: true, description: 'multiple chars' },
])('isNonEmpty', ({ input, expected, description }) => {
it(`should return ${expected} for ${description}`, () => {
expect(isNonEmpty(input)).toBe(expected);
});
});
| Anti-Pattern | Problem | Better Approach |
|---|---|---|
| Testing implementation | Brittle tests | Test behavior/output |
| Shared mutable state | Test interference | Fresh setup per test |
| Over-mocking | Tests don't reflect reality | Mock only boundaries |
| Giant test files | Hard to maintain | Group logically, split |
| No assertions | False confidence | Always assert |
| Testing framework code | Waste of time | Trust the framework |
| Agent | Unit Testing Use |
|---|---|
| Senior Developer | Primary: Writes unit tests BEFORE implementation (TDD Red phase), reviews test quality |
| Junior Developer | Implements code to pass tests (TDD Green phase), does NOT write initial unit tests |
| QA Engineer | Reviews coverage, identifies gaps (writes integration/E2E tests, not unit tests) |
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.