5.6 KiB
5.6 KiB
Backend Test Coverage
Test Runner
The backend uses Node.js built-in test runner (node:test) with tsx for TypeScript execution.
npm run test # Run all tests
npm run verify # Typecheck + lint + tests
Test Structure
Tests are colocated with source files using the .test.ts suffix:
src/
├── services/
│ ├── auth.ts
│ ├── auth.test.ts # Auth service tests
│ └── shared/
│ ├── crud-service.ts
│ ├── crud-service.test.ts
│ └── role-policy.test.ts
├── api/controllers/
│ ├── auth.controller.ts
│ └── auth.controller.test.ts
├── middlewares/
│ ├── error-handler.ts
│ └── error-handler.test.ts
├── db/api/shared/
│ ├── repository.ts
│ └── repository.test.ts
└── test-utils/
└── index.ts # Shared test utilities
Test Utilities
Located in src/test-utils/index.ts:
Test Data Builders
import { createTestUser, createGlobalAccessUser } from '@/test-utils';
// Create a standard test user
const user = createTestUser();
// Create user with global access
const admin = createGlobalAccessUser();
// Override specific properties
const customUser = createTestUser({
organizationId: 'custom-org',
app_role: { name: 'director', globalAccess: false },
});
Mock DB API Factory
import { createMockDbApi } from '@/test-utils';
// Create a mock with default behavior
const mockDbApi = createMockDbApi();
// Customize responses
const mockDbApi = createMockDbApi({
findBy: async (where) => where.id === 'exists' ? { id: 'exists' } : null,
});
// Check calls
expect(mockDbApi.calls.create.length).toBe(1);
mockDbApi.reset(); // Clear call history
Mock Request/Response
import { createMockRequest } from '@/test-utils';
const req = createMockRequest({
body: { email: 'test@example.com' },
currentUser: createTestUser(),
});
Current Coverage
Services
| File | Description | Tests |
|---|---|---|
services/auth.test.ts |
Auth helpers and service methods | ~40 |
services/shared/crud-service.test.ts |
CRUD factory | ~20 |
services/shared/role-policy.test.ts |
Role constraints | ~10 |
services/shared/audio-access.test.ts |
Audio-library visibility/management rules | ~12 |
services/refresh-token-maintenance.test.ts |
Refresh-token retention cutoff + cleanup orchestration (mocked DB API) | ~4 |
Domain constants / pure rules
| File | Description | Tests |
|---|---|---|
shared/constants/audio-files.test.ts |
file/url/recipe kinds + isAudioFileKind |
~2 |
shared/constants/policy-documents.test.ts |
category validation + version-bump re-acknowledgment rule | ~several |
shared/constants/users.test.ts |
honorific name-prefix formatting (formatPersonName) |
~several |
Controllers
| File | Description | Tests |
|---|---|---|
api/controllers/auth.controller.test.ts |
Auth endpoints | ~20 |
api/controllers/campus_attendance.controller.test.ts |
Attendance endpoints | ~10 |
Infrastructure
| File | Description | Tests |
|---|---|---|
middlewares/error-handler.test.ts |
Error normalization | ~10 |
db/api/shared/repository.test.ts |
Repository base | ~10 |
shared/architecture/import-boundaries.test.ts |
Architecture validation | ~5 |
Testing Patterns
Pure Function Tests
Test pure functions directly without mocking:
import { test } from 'node:test';
import assert from 'node:assert/strict';
test('formats user display name', () => {
const result = formatDisplayName('John', 'Doe');
assert.equal(result, 'John Doe');
});
Service Tests with Mocked DB APIs
Mock the data layer to test service logic:
import { test, describe, mock, beforeEach } from 'node:test';
import assert from 'node:assert/strict';
describe('UserService', () => {
let mockDbApi: ReturnType<typeof createMockDbApi>;
beforeEach(() => {
mockDbApi = createMockDbApi();
});
test('creates user with hashed password', async () => {
mockDbApi.create.mock.mockImplementation(async (data) => ({
id: 'new-user',
...data,
}));
await createUser({ email: 'new@example.com', password: 'secret' });
assert.equal(mockDbApi.calls.create.length, 1);
const [data] = mockDbApi.calls.create[0];
assert.notEqual(data.password, 'secret'); // Should be hashed
});
});
Controller Tests
Mock services and test request/response handling:
import { test, describe, mock } from 'node:test';
import assert from 'node:assert/strict';
describe('auth controller', () => {
test('returns user profile on successful signin', async () => {
const req = createMockRequest({
body: { email: 'test@example.com', password: 'password' },
});
const res = createMockResponse();
await signinHandler(req, res, mockAuthService);
assert.equal(res.statusCode, 200);
assert.equal(res.body.email, 'test@example.com');
});
});
Adding New Tests
- Create a
.test.tsfile next to the source file - Import from
node:testandnode:assert/strict - Use
@/test-utilsfor common setup - Follow the describe/test structure
- Run
npm run testto verify
Best Practices
- Test behavior, not implementation details
- Use descriptive test names that explain the expected behavior
- Keep tests focused - one assertion per test when possible
- Mock at the boundary (DB APIs, external services)
- Use
beforeEachto reset mocks between tests - Prefer
assert.deepEqualfor objects,assert.equalfor primitives