From backend-overture
This skill provides backend testing rules with Vitest, real DB/API integration patterns, and service-level testing conventions. Automatically loaded when writing backend tests, reviewing test quality, or when "backend test", "service test", "API test", "integration test", "database test", or "backend test coverage" are mentioned.
How this skill is triggered — by the user, by Claude, or both
Slash command
/backend-overture:typescript-testing-backendThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
- **Vitest**: Primary test runner
import { describe, it, expect, beforeEach, vi } from 'vitest'vi.mock()Mandatory: Unit test coverage must be 70% or higher
Layer-specific targets:
Metrics: Statements, Branches, Functions, Lines
Unit Tests
Integration Tests
Property-Based Tests (fast-check)
Recommended Principle: Always start code changes with tests
Development Steps:
NG Cases (Test-first not required):
__tests__/ directories or co-located with sourceRecommended: Mock external dependencies in unit tests
For integration tests: Use real dependencies
Fix tests: Wrong expected values, implementation detail coupling, flaky assertions Fix implementation: Valid business rules, edge cases, contract violations When in doubt: Confirm with user
src/
├── users/
│ ├── users.service.ts
│ ├── users.controller.ts
│ ├── __tests__/
│ │ ├── users.service.test.ts
│ │ ├── users.controller.test.ts
│ │ └── users.integration.test.ts
│ └── index.ts
Rationale:
__tests__/ directory convention for backend projects{module}.{layer}.test.ts (e.g., users.service.test.ts){feature}.integration.test.tsRecommended: Keep all tests always active
Avoid: test.skip() or commenting out
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { UserService } from '../users.service'
describe('UserService', () => {
let service: UserService
let mockRepository: { findOne: ReturnType<typeof vi.fn>; save: ReturnType<typeof vi.fn> }
beforeEach(() => {
mockRepository = {
findOne: vi.fn(),
save: vi.fn(),
}
service = new UserService(mockRepository as any)
})
it('should return user when found', async () => {
const expectedUser = { id: '1', name: 'John', email: '[email protected]' }
mockRepository.findOne.mockResolvedValue(expectedUser)
const result = await service.findById('1')
expect(result).toEqual(expectedUser)
expect(mockRepository.findOne).toHaveBeenCalledWith({ where: { id: '1' } })
})
it('should throw NotFoundError when user not found', async () => {
mockRepository.findOne.mockResolvedValue(null)
await expect(service.findById('999')).rejects.toThrow(NotFoundError)
})
})
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import request from 'supertest'
import { createApp } from '../app'
describe('POST /api/users', () => {
let app: Express
beforeAll(async () => {
app = await createApp({ database: 'test' })
})
afterAll(async () => {
await app.close()
})
it('should create user and return 201', async () => {
const response = await request(app)
.post('/api/users')
.send({ name: 'Jane', email: '[email protected]' })
.expect(201)
expect(response.body).toMatchObject({
name: 'Jane',
email: '[email protected]',
})
expect(response.body.id).toBeDefined()
})
it('should return 400 for invalid email', async () => {
await request(app)
.post('/api/users')
.send({ name: 'Jane', email: 'not-an-email' })
.expect(400)
})
})
import { describe, it, expect } from 'vitest'
import fc from 'fast-check'
import { parseUserId } from '../utils/parse-user-id'
describe('parseUserId', () => {
it('should round-trip: format(parse(id)) === id for valid UUIDs', () => {
fc.assert(
fc.property(fc.uuid(), (uuid) => {
const parsed = parseUserId(uuid)
expect(parsed.toString()).toBe(uuid)
})
)
})
it('should reject non-UUID strings', () => {
fc.assert(
fc.property(
fc.string().filter((s) => !isValidUuid(s)),
(invalidId) => {
expect(() => parseUserId(invalidId)).toThrow()
}
)
)
})
})
Use hardcoded literal values for assertions:
expect(calculatePrice(100, 0.1)).toBe(110)
expect(formatDate(new Date('2025-01-15'))).toBe('2025-01-15')
expect(user.role).toBe('admin')
Verify final results and outcomes:
expect(mockRepository.save).toHaveBeenCalledWith({ name: 'test', email: '[email protected]' })
expect(result).toEqual({ id: '1', status: 'created' })
expect(response.statusCode).toBe(201)
Every test must include at least one expect() that validates observable behavior.
Mock only direct external I/O dependencies. Internal utilities use real implementations:
vi.mock('../repositories/user.repository') // External I/O — mock
vi.mock('../clients/email.client') // External API — mock
// Internal validators, formatters, mappers — use real implementations
beforeEach(async () => {
await dataSource.query('BEGIN')
})
afterEach(async () => {
await dataSource.query('ROLLBACK')
})
npx claudepluginhub tundraray/overture --plugin backend-overtureTests backend APIs with vitest/jest, go test, pytest, cargo test; verifies endpoints, DB changes, errors, collects evidence. Prohibits curl; mandates pre-completion verification.
Guides writing and debugging TypeScript/NestJS unit tests with Jest, DeepMocked createMock, and in-memory databases. Activates on .spec.ts files or testing workflows.
Provides testing pyramid, unit patterns (AAA, isolation, parameterized, edge cases), and React Testing Library for component tests. Use when writing tests or setting up testing infrastructure.