| name | testing-unit-vitest |
| description | Writes and maintains unit tests using Vitest. To be used for testing business logic in services and utilities. |
Unit Testing Skill
When asked to write or maintain unit tests, follow these guidelines:
When to Write Unit Tests
- New services/utilities: Write tests when creating new business logic
- Bug fixes: Add tests to verify bug resolution and prevent regression
- Complex logic: Test edge cases, boundary conditions, and error handling
- Refactoring: Ensure tests pass before and after code changes
File Naming and Location
- Colocate tests: Place test files next to source files (e.g.,
validation.spec.ts next to validation.ts)
- Naming convention: Use
{filename}.spec.ts pattern
- Separate E2E: Keep Playwright tests in
tests/ directory for HTTP-layer integration tests
Test Structure
Use describe/it blocks with arrange-act-assert pattern:
import { describe, it, expect, beforeEach, vi } from 'vitest';
describe('MyService', () => {
beforeEach(() => {
});
describe('methodName', () => {
it('should do something specific', () => {
const input = { };
const result = service.method(input);
expect(result).toBe(expected);
});
});
});
Mocking Dependencies
Use vi.fn() for repository interfaces and external services:
import { vi } from 'vitest';
const mockRepo = {
save: vi.fn(),
findById: vi.fn(),
findAll: vi.fn(),
};
const service = new MyService(mockRepo);
vi.mocked(mockRepo.findById).mockReturnValue(mockData);
expect(mockRepo.save).toHaveBeenCalledWith(expected);
expect(mockRepo.save).toHaveBeenCalledTimes(1);
Running Tests
- Watch mode:
npm run test:dev - Auto-rerun tests during development
- One-time:
npm run test:unit - Run all unit tests once
- Coverage:
npm run test:coverage - Generate coverage report
- E2E:
npm test - Run Playwright tests independently
Coverage Expectations
- Services/utilities: Aim for >80% coverage
- Edge cases: Test boundary conditions (0, 1, max, max+1)
- Error paths: Verify error handling and validation
- Business rules: Test all conditional logic and state transitions
Best Practices
- Test behavior, not implementation: Focus on inputs/outputs, not internal details
- One assertion per test: Keep tests focused (exceptions for related checks)
- Clear test names: Use descriptive
it('should...') statements
- Isolated tests: Each test should run independently
- Fast execution: Mock I/O operations to keep tests fast
Common Vitest Matchers
expect(value).toBe(expected);
expect(value).toEqual(expected);
expect(value).toBeInstanceOf(Class);
expect(value).toBeUndefined();
expect(array).toHaveLength(3);
expect(() => fn()).toThrow(ErrorClass);
expect(mockFn).toHaveBeenCalledWith(arg);
expect(mockFn).toHaveBeenCalledTimes(1);
Reference
For advanced features, see Vitest documentation:
- Snapshot testing
- Testing async code
- Custom matchers
- Performance testing