Testing Practices
Test File Policy
CRITICAL: NEVER modify test files when working on code changes unless explicitly asked. This ensures existing tests validate that code changes are correct.
Test File Organization
Backend Tests
code
src/
├── feature/
│ ├── feature.service.ts
│ ├── feature.service.spec.ts # Unit tests (alongside source)
│ └── feature.controller.spec.ts
└── test/
└── e2e/ # E2E tests
└── feature.e2e-spec.ts
Frontend Tests
code
app/
├── components/
│ └── Component/
│ ├── Component.tsx
│ └── Component.test.tsx # Component tests
└── __tests__/ # Integration tests
└── feature.test.tsx
Unit Test Pattern (Backend)
typescript
// feature.service.spec.ts
import { Test } from '@nestjs/testing';
import { getModelToken } from '@nestjs/mongoose';
import { FeatureService } from './feature.service';
describe('FeatureService', () => {
let service: FeatureService;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
FeatureService,
{ provide: getModelToken('FeatureV2'), useValue: { create: jest.fn() } },
],
}).compile();
service = module.get<FeatureService>(FeatureService);
});
it('should create with tenant_id', async () => {
const dto = { name: 'Test' };
const user = { tenant_id: 't1', userId: 'u1' };
const result = await service.create(dto, user);
expect(result.tenant_id).toBe(user.tenant_id);
});
});
Component Test Pattern (Frontend)
typescript
// Component.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { Component } from './Component';
describe('Component', () => {
it('renders title correctly', () => {
render(<Component title="Test Title" />);
expect(screen.getByText('Test Title')).toBeInTheDocument();
});
it('calls onAction when button is clicked', () => {
const mockAction = jest.fn();
render(<Component title="Test" onAction={mockAction} />);
fireEvent.click(screen.getByRole('button'));
expect(mockAction).toHaveBeenCalledTimes(1);
});
});
Mocking Patterns
Backend: MongoDB Models
typescript
const mockModel = {
find: jest.fn().mockReturnValue({
exec: jest.fn().mockResolvedValue([{ id: '1', name: 'Test' }]),
}),
findOne: jest.fn().mockReturnValue({
exec: jest.fn().mockResolvedValue({ id: '1', name: 'Test' }),
}),
create: jest.fn().mockResolvedValue({ id: '1', name: 'Test' }),
findByIdAndUpdate: jest.fn().mockResolvedValue({ id: '1', name: 'Updated' }),
};
Frontend: API Calls
typescript
jest.mock('@/app/api/feature/feature', () => ({
featureApi: {
getAll: jest.fn().mockResolvedValue([{ id: '1', name: 'Test' }]),
create: jest.fn().mockResolvedValue({ id: '1', name: 'New' }),
},
}));
Frontend: React Query
typescript
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const createTestQueryClient = () =>
new QueryClient({
defaultOptions: {
queries: { retry: false },
mutations: { retry: false },
},
});
const renderWithQuery = (component: React.ReactElement) => {
const queryClient = createTestQueryClient();
return render(
<QueryClientProvider client={queryClient}>{component}</QueryClientProvider>,
);
};
Test Coverage Requirements
- •Minimum coverage: 80%
- •Critical paths: 100% coverage (auth, payments, stock calculations)
- •Focus areas: Business logic, services, utilities
- •Skip coverage: DTOs (validation only), simple getters/setters
Testing Multi-Tenancy
Always test tenant isolation: Create data for multiple tenants, verify queries only return data for the requesting tenant.
Testing Error Cases
Test error scenarios: NotFoundException, ConflictException, ValidationException. Use rejects.toThrow() for async errors.
E2E Test Pattern
typescript
// test/e2e/feature.e2e-spec.ts
import { Test } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
describe('FeatureController (e2e)', () => {
let app: INestApplication;
let authToken: string;
beforeAll(async () => {
const module = await Test.createTestingModule({ imports: [AppModule] }).compile();
app = module.createNestApplication();
await app.init();
const res = await request(app.getHttpServer()).post('/api/auth/login').send({ email: 'test@example.com', password: 'password' });
authToken = res.body.token;
});
it('/api/feature (GET)', () => {
return request(app.getHttpServer()).get('/api/feature').set('Authorization', `Bearer ${authToken}`).expect(200);
});
});
Running Tests
Backend: npm test | npm run test:e2e | npm run test:cov
Frontend: npm test | npm run test:watch | npm run test:coverage
Best Practices
- •Test behavior, not implementation
- •Use descriptive test names
- •Arrange-Act-Assert structure
- •Mock external dependencies
- •Test edge cases: null, empty, invalid inputs
- •Keep tests fast (milliseconds)
- •One assertion per test when possible
- •Don't modify existing tests unless explicitly asked