Claude
Skills
Sign in
Back

typescript-testing-backend

Included with Lifetime
$97 forever

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.

Backend & APIs

What this skill does


# TypeScript Testing Rules (Backend)

## Test Framework

- **Vitest**: Primary test runner
- **Supertest**: For HTTP endpoint testing
- **fast-check**: For property-based testing
- Test imports: `import { describe, it, expect, beforeEach, vi } from 'vitest'`
- Mock creation: Use `vi.mock()`

## Basic Testing Policy

### Quality Requirements

- **Coverage**: Unit test coverage must be 70% or higher (backend standard)
- **Independence**: Each test can run independently without depending on other tests
- **Reproducibility**: Tests are environment-independent and always return the same results
- **Readability**: Test code maintains the same quality as production code

### Coverage Requirements

**Mandatory**: Unit test coverage must be 70% or higher

**Layer-specific targets**:
- Services (Business Logic): 80% or higher
- Controllers (API Layer): 70% or higher
- Repositories (Data Access): 60% or higher — focus on custom query methods
- Utils/Helpers: 80% or higher
- Guards/Interceptors/Pipes: 70% or higher

**Metrics**: Statements, Branches, Functions, Lines

### Test Types and Scope

1. **Unit Tests**
   - Verify behavior of individual services, functions, or classes
   - Mock all external dependencies (DB, APIs, message queues)
   - Most numerous, implemented with fine granularity
   - Focus on business logic correctness

2. **Integration Tests**
   - Verify coordination between layers (Controller → Service → Repository)
   - Use real database (test instance) or in-memory database
   - Use real HTTP through Supertest
   - Test API contracts end-to-end within the service

3. **Property-Based Tests (fast-check)**
   - Verify invariants hold across random inputs
   - Use for: parsers, validators, serializers, mathematical operations
   - Complement example-based tests — do not replace them

## Red-Green-Refactor Process (Test-First Development)

**Recommended Principle**: Always start code changes with tests

**Development Steps**:
1. **Red**: Write test for expected behavior (it fails)
2. **Green**: Pass test with minimal implementation
3. **Refactor**: Improve code while maintaining passing tests

**NG Cases (Test-first not required)**:
- Pure configuration file changes (nest-cli.json, ormconfig.ts, etc.)
- Documentation-only updates
- Emergency production incident response (post-incident tests mandatory)

## Test Design Principles

### Test Case Structure

- Tests consist of three stages: "Arrange," "Act," "Assert"
- Clear naming that shows purpose of each test
- One test case verifies only one behavior

### Test Data Management

- Manage test data in `__tests__/` directories or co-located with source
- Use factory functions or builder patterns for test data creation
- Always mock sensitive information (API keys, credentials)
- Keep test data minimal — only data directly related to test verification
- Use database fixtures or seeding for integration tests

### Mock and Stub Usage Policy

**Recommended: Mock external dependencies in unit tests**
- Mock database connections, external API clients, message queues
- Mock at the boundary — not internal functions

**For integration tests: Use real dependencies**
- Real database (test instance or in-memory like SQLite)
- Real HTTP stack through Supertest
- Mock only truly external services (third-party APIs)

### Test Failure Response Decision Criteria

**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

## Test Implementation Conventions

### Directory Structure

```
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
- Clear separation between unit and integration tests
- Easy to find and maintain tests alongside implementation

### Naming Conventions

- Test files: `{module}.{layer}.test.ts` (e.g., `users.service.test.ts`)
- Integration test files: `{feature}.integration.test.ts`
- Test suites: Names describing target module or feature
- Test cases: Names describing expected behavior from caller perspective

### Test Code Quality Rules

**Recommended: Keep all tests always active**
- Fix problematic tests and activate them

**Avoid: `test.skip()` or commenting out**
- Creates test gaps and incomplete quality checks
- Solution: Completely delete unnecessary tests

## Service Testing Patterns

### Unit Testing Services

```typescript
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)
  })
})
```

### Integration Testing with Supertest

```typescript
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)
  })
})
```

### Property-Based Testing with fast-check

```typescript
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()
        }
      )
    )
  })
})
```

## Test Quality Criteria

### Literal Expected Values

Use hardcoded literal values for assertions:
```typescript
expect(calculatePrice(100, 0.1)).toBe(110)
expect(formatDate(new Date('2025-01-15'))).toBe('2025-01-15')
expect(user.role).toBe('admin')
```

### Result-Based Verification

Verify final results and outcomes:
```typescript
expect(mockRepository.save).toHaveBeenCalledWith({ name: 'test', email: '[email protected]' })
expect(result).toEqual({ id: '1', status: 'created' })
expect(response.statusCode).toBe(201)
```

### Meaningful Assertions

Every test must include at least one `expect()` that validates observable behavior.

### Appropriate Mock Scope

Mock only direct external I/O dependencies. Internal utilities use re

Related in Backend & APIs