Testing Standards
Rules for writing and maintaining tests. Quality tests are as important as quality code.
AAA Pattern
Structure every test with Arrange, Act, Assert, separated by blank lines:
it("should return user profile when valid ID is provided", async () => {
// Arrange
const userId = "user-123";
const expectedUser = { id: userId, name: "Jane Doe", email: "jane@example.com" };
mockUserRepo.findById.mockResolvedValue(expectedUser);
// Act
const result = await userService.getProfile(userId);
// Assert
expect(result).toEqual(expectedUser);
expect(mockUserRepo.findById).toHaveBeenCalledWith(userId);
});
Test Naming
Pattern: should [expected behavior] when [condition]. describe blocks name the unit under test.
// Good
"should throw NotFoundError when user does not exist"
"should return paginated results when page parameter is provided"
// Bad
"test user" "works correctly" "error case"
describe("UserService", () => {
describe("getProfile", () => {
it("should return user profile when valid ID is provided", ...);
it("should throw NotFoundError when user does not exist", ...);
});
});
One Assertion Concept Per Test
- Each test verifies one logical concept, not one literal assertion.
- Multiple related assertions on the same result are fine (status code + response body).
- If a test needs a second "Arrange" for a second check, it is a second test.
Test Independence
- Every test must pass in isolation and in any order. No interdependency.
- No shared mutable state.
beforeEach for setup, never beforeAll with mutation.
- Each test builds its own data — use factory functions/builders with overrides
(
buildUser({ role: "admin" }), create(:customer, :member)).
Mocking Strategy
- Mock external dependencies: databases, APIs, file systems, email services, third-party SDKs.
- Do not mock internal logic. If you must mock internal functions to test a unit, the design
needs refactoring — inject the dependency instead.
- Prefer stubs and fakes over complex mock chains. Painful mocking is a design signal.
- Reset mocks between tests:
beforeEach(() => vi.clearAllMocks()).
Coverage Targets
- Business logic: 80% minimum — services, domain models, validators, utilities.
- Overall project: 60% minimum — including infrastructure, config, glue code.
- Critical paths: 100% target — authentication, authorization, payments, data validation.
- Coverage is a floor, not a ceiling. Focus on branch coverage, not line coverage. High coverage
with weak assertions is worse than moderate coverage with meaningful tests.
Test Types
- Unit: fast, isolated, no I/O. One module. The base of the pyramid — many of these.
- Integration: real DB / real router across modules. Some of these.
- E2E: complete user flows, critical paths only. Slow and brittle by nature — few of these.
- Contract: validate API contracts at service boundaries (Pact or similar).
Edge Cases
Every suite covers all five: null/undefined, empty (string/array/zero), boundary
(min/max, pagination and length limits), error paths (network failure, invalid data,
unauthorized, timeout), and concurrency where applicable.
Anti-Patterns to Avoid
- Testing implementation details — test behavior, not internal calls or private state.
- Snapshot overuse — snapshots hide intent and fail on cosmetic changes. Use sparingly.
- Flaky tests — fix or delete immediately. A flaky test is worse than no test.
- Commented-out tests — delete them; version control keeps history.
- Testing framework code — do not test that your ORM saves or your HTTP library sends requests.
Deep guides (read on demand, do not preload)
- Unit vs integration, mock boundaries, test data builders, edge-case matrix, Sidekiq jobs →
references/test-strategy.md
- Vitest + RTL setup, query priority, userEvent, MSW, providers, Zustand, Framer Motion, ApexCharts →
references/react-components.md
- Next.js Server Components, server actions,
generateMetadata, route handlers → references/nextjs-server.md
- React Native: RNTL, navigation, Reanimated, MMKV, Centrifugo →
references/react-native.md
1---2name: std-testing3description: Testing standards — AAA pattern, naming, mocking, coverage targets, Vitest + RTL, edge cases. Use when writing tests or test infrastructure.4---56# Testing Standards78Rules for writing and maintaining tests. Quality tests are as important as quality code.910## AAA Pattern1112Structure every test with Arrange, Act, Assert, separated by blank lines:1314```typescript15it("should return user profile when valid ID is provided", async () => {16 // Arrange17 const userId = "user-123";18 const expectedUser = { id: userId, name: "Jane Doe", email: "jane@example.com" };19 mockUserRepo.findById.mockResolvedValue(expectedUser);2021 // Act22 const result = await userService.getProfile(userId);2324 // Assert25 expect(result).toEqual(expectedUser);26 expect(mockUserRepo.findById).toHaveBeenCalledWith(userId);27});28```2930## Test Naming3132Pattern: `should [expected behavior] when [condition]`. `describe` blocks name the unit under test.3334```typescript35// Good36"should throw NotFoundError when user does not exist"37"should return paginated results when page parameter is provided"3839// Bad40"test user" "works correctly" "error case"41```4243```typescript44describe("UserService", () => {45 describe("getProfile", () => {46 it("should return user profile when valid ID is provided", ...);47 it("should throw NotFoundError when user does not exist", ...);48 });49});50```5152## One Assertion Concept Per Test5354- Each test verifies one logical concept, not one literal assertion.55- Multiple related assertions on the same result are fine (status code + response body).56- If a test needs a second "Arrange" for a second check, it is a second test.5758## Test Independence5960- Every test must pass in isolation and in any order. No interdependency.61- No shared mutable state. `beforeEach` for setup, never `beforeAll` with mutation.62- Each test builds its own data — use factory functions/builders with overrides63 (`buildUser({ role: "admin" })`, `create(:customer, :member)`).6465## Mocking Strategy6667- **Mock external dependencies**: databases, APIs, file systems, email services, third-party SDKs.68- **Do not mock internal logic.** If you must mock internal functions to test a unit, the design69 needs refactoring — inject the dependency instead.70- Prefer stubs and fakes over complex mock chains. Painful mocking is a design signal.71- Reset mocks between tests: `beforeEach(() => vi.clearAllMocks())`.7273## Coverage Targets7475- **Business logic**: 80% minimum — services, domain models, validators, utilities.76- **Overall project**: 60% minimum — including infrastructure, config, glue code.77- **Critical paths**: 100% target — authentication, authorization, payments, data validation.78- Coverage is a floor, not a ceiling. Focus on **branch** coverage, not line coverage. High coverage79 with weak assertions is worse than moderate coverage with meaningful tests.8081## Test Types8283- **Unit**: fast, isolated, no I/O. One module. The base of the pyramid — many of these.84- **Integration**: real DB / real router across modules. Some of these.85- **E2E**: complete user flows, critical paths only. Slow and brittle by nature — few of these.86- **Contract**: validate API contracts at service boundaries (Pact or similar).8788## Edge Cases8990Every suite covers all five: **null/undefined**, **empty** (string/array/zero), **boundary**91(min/max, pagination and length limits), **error paths** (network failure, invalid data,92unauthorized, timeout), and **concurrency** where applicable.9394## Anti-Patterns to Avoid9596- **Testing implementation details** — test behavior, not internal calls or private state.97- **Snapshot overuse** — snapshots hide intent and fail on cosmetic changes. Use sparingly.98- **Flaky tests** — fix or delete immediately. A flaky test is worse than no test.99- **Commented-out tests** — delete them; version control keeps history.100- **Testing framework code** — do not test that your ORM saves or your HTTP library sends requests.101102## Deep guides (read on demand, do not preload)103104- Unit vs integration, mock boundaries, test data builders, edge-case matrix, Sidekiq jobs → `references/test-strategy.md`105- Vitest + RTL setup, query priority, userEvent, MSW, providers, Zustand, Framer Motion, ApexCharts → `references/react-components.md`106- Next.js Server Components, server actions, `generateMetadata`, route handlers → `references/nextjs-server.md`107- React Native: RNTL, navigation, Reanimated, MMKV, Centrifugo → `references/react-native.md`