unit-testing-patterns
SkillWeb & browsingUnit testing patterns for backend and frontend. AAA structure, effective mocking, test isolation.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the unit-testing-patterns skill
What this skill tells your AI
The instructions your AI receives, as published by porcupine-md/anoa-browser in .claude/skills/library/testing/unit-testing-patterns/SKILL.md and read by ahel’s review.
The AAA Pattern
Every test follows Arrange-Act-Assert:
it('creates a user with hashed password', async () => {
// ARRANGE — set up inputs and mocks
const dto = { email: 'test@example.com', password: 'plaintext' };
const mockRepo = { save: vi.fn().mockResolvedValue({ id: '1', ...dto }) };
const service = new UserService(mockRepo);
// ACT — call the thing under test
const user = await service.createUser(dto);
// ASSERT — verify the outcome
expect(user.id).toBe('1');
expect(mockRepo.save).toHaveBeenCalledWith(
expect.objectContaining({ email: dto.email })
);
expect(mockRepo.save.mock.calls[0][0].password).not.toBe('plaintext');
});
What to Mock
Mock: External I/O — database, network, filesystem, timers, random values
Don't mock: Pure functions, your own business logic, constants
// GOOD: mock the database (I/O)
vi.mock('./db', () => ({ findUser: vi.fn() }));
// BAD: mock your own service (you'd be testing the mock, not the code)
vi.mock('./userService');
Mocking Strategies
Spies (observe without replacing)
const logSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
// ... do something
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('Error'));
logSpy.mockRestore();
Stubs (replace with controlled value)
const getTimeSpy = vi.spyOn(Date, 'now').mockReturnValue(1234567890);
Module mocks
vi.mock('./email-service', () => ({
sendEmail: vi.fn().mockResolvedValue({ success: true }),
}));
Test Isolation
describe('UserService', () => {
let service: UserService;
let mockRepo: ReturnType<typeof createMockRepo>;
beforeEach(() => {
// Fresh instance per test — no shared state
mockRepo = createMockRepo();
service = new UserService(mockRepo);
});
afterEach(() => {
vi.clearAllMocks();
});
});
Assertion Best Practices
// Be specific — assert exact values when meaningful
expect(result.count).toBe(3); // not toBeTruthy()
// Use asymmetric matchers for dynamic values
expect(result).toEqual(expect.objectContaining({
id: expect.any(String),
createdAt: expect.any(Date),
name: 'Alice',
}));
// Test error type specifically
await expect(service.delete('nonexistent')).rejects.toThrow(NotFoundError);
// Don't over-assert (don't test internals)
// BAD: testing private method was called
// GOOD: testing the public observable outcome
Test Naming
// Format: [unit] [scenario] [expected result]
it('returns null when user is not found')
it('throws UnauthorizedError when password is wrong')
it('sends welcome email after registration')
it('does not send email when dry-run mode is enabled')
File Organization
src/
users/
users.service.ts
users.service.test.ts ← co-located
users.repository.ts
users.repository.test.ts ← co-located
Coverage Thresholds
Minimum per module: 80% Critical paths (auth, payments): 95%
// vitest.config.ts
coverage: {
thresholds: { lines: 80, functions: 80, branches: 75 }
}
Signals
- GitHub stars
- 23
- Forks
- 2
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
unit-testing-patterns- Source
- github.com/porcupine-md/anoa-browser