Testing
APSO-generated projects come pre-configured with Jest for testing. This guide covers testing strategies for your API.
Test Structure
src/
├── modules/
│ ├── projects/
│ │ ├── projects.controller.ts
│ │ ├── projects.service.ts
│ │ ├── projects.controller.spec.ts # Unit tests
│ │ └── projects.service.spec.ts # Unit tests
│ └── ...
└── test/
├── app.e2e-spec.ts # E2E tests
└── jest-e2e.json # E2E configRunning Tests
# Unit tests
npm run test
# Unit tests with coverage
npm run test:cov
# E2E tests
npm run test:e2e
# Watch mode
npm run test:watchUnit Testing
Testing Services
// projects.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { ProjectsService } from './projects.service';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Project } from './entities/project.entity';
describe('ProjectsService', () => {
let service: ProjectsService;
let mockRepository: jest.Mocked<Repository<Project>>;
beforeEach(async () => {
mockRepository = {
find: jest.fn(),
findOne: jest.fn(),
save: jest.fn(),
delete: jest.fn(),
} as any;
const module: TestingModule = await Test.createTestingModule({
providers: [
ProjectsService,
{
provide: getRepositoryToken(Project),
useValue: mockRepository,
},
],
}).compile();
service = module.get<ProjectsService>(ProjectsService);
});
describe('findAll', () => {
it('should return all projects for an organization', async () => {
const mockProjects = [
{ id: '1', name: 'Project 1', organizationId: 'org-1' },
{ id: '2', name: 'Project 2', organizationId: 'org-1' },
];
mockRepository.find.mockResolvedValue(mockProjects);
const result = await service.findAll({ organizationId: 'org-1' });
expect(result).toEqual(mockProjects);
expect(mockRepository.find).toHaveBeenCalledWith({
where: { organizationId: 'org-1' },
});
});
});
describe('create', () => {
it('should create a new project', async () => {
const createDto = { name: 'New Project' };
const context = { organizationId: 'org-1', userId: 'user-1' };
const savedProject = { id: '1', ...createDto, ...context };
mockRepository.save.mockResolvedValue(savedProject);
const result = await service.create(createDto, context);
expect(result).toEqual(savedProject);
expect(mockRepository.save).toHaveBeenCalledWith({
...createDto,
organizationId: 'org-1',
});
});
});
});Testing Controllers
// projects.controller.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { ProjectsController } from './projects.controller';
import { ProjectsService } from './projects.service';
describe('ProjectsController', () => {
let controller: ProjectsController;
let mockService: jest.Mocked<ProjectsService>;
beforeEach(async () => {
mockService = {
findAll: jest.fn(),
findOne: jest.fn(),
create: jest.fn(),
update: jest.fn(),
remove: jest.fn(),
} as any;
const module: TestingModule = await Test.createTestingModule({
controllers: [ProjectsController],
providers: [
{
provide: ProjectsService,
useValue: mockService,
},
],
}).compile();
controller = module.get<ProjectsController>(ProjectsController);
});
describe('findAll', () => {
it('should return paginated projects', async () => {
const mockResult = {
data: [{ id: '1', name: 'Project' }],
total: 1,
};
mockService.findAll.mockResolvedValue(mockResult);
const result = await controller.findAll(
{ limit: 10, offset: 0 },
{ organizationId: 'org-1' },
);
expect(result).toEqual(mockResult);
});
});
});Integration Testing
Test multiple components together with a real database:
// projects.integration.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ProjectsModule } from './projects.module';
import { ProjectsService } from './projects.service';
describe('Projects Integration', () => {
let module: TestingModule;
let service: ProjectsService;
beforeAll(async () => {
module = await Test.createTestingModule({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
url: process.env.TEST_DATABASE_URL,
autoLoadEntities: true,
synchronize: true,
}),
ProjectsModule,
],
}).compile();
service = module.get<ProjectsService>(ProjectsService);
});
afterAll(async () => {
await module.close();
});
beforeEach(async () => {
// Clean up before each test
await service.deleteAll();
});
it('should create and retrieve a project', async () => {
const created = await service.create(
{ name: 'Test Project' },
{ organizationId: 'org-1' },
);
const found = await service.findOne(created.id, { organizationId: 'org-1' });
expect(found.name).toBe('Test Project');
expect(found.organizationId).toBe('org-1');
});
});E2E Testing
Test the full API with HTTP requests:
// test/app.e2e-spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
describe('Projects API (e2e)', () => {
let app: INestApplication;
let authToken: string;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
// Get auth token
const loginResponse = await request(app.getHttpServer())
.post('/auth/login')
.send({ email: 'test@example.com', password: 'password' });
authToken = loginResponse.body.token;
});
afterAll(async () => {
await app.close();
});
describe('GET /projects', () => {
it('should return 401 without auth', () => {
return request(app.getHttpServer())
.get('/api/v1/projects')
.expect(401);
});
it('should return projects with auth', () => {
return request(app.getHttpServer())
.get('/api/v1/projects')
.set('Authorization', `Bearer ${authToken}`)
.expect(200)
.expect((res) => {
expect(res.body).toHaveProperty('data');
expect(Array.isArray(res.body.data)).toBe(true);
});
});
});
describe('POST /projects', () => {
it('should create a project', () => {
return request(app.getHttpServer())
.post('/api/v1/projects')
.set('Authorization', `Bearer ${authToken}`)
.send({ name: 'New Project' })
.expect(201)
.expect((res) => {
expect(res.body.name).toBe('New Project');
expect(res.body.id).toBeDefined();
});
});
it('should validate required fields', () => {
return request(app.getHttpServer())
.post('/api/v1/projects')
.set('Authorization', `Bearer ${authToken}`)
.send({})
.expect(400)
.expect((res) => {
expect(res.body.message).toContain('name');
});
});
});
});Test Database Setup
Using Docker
# docker-compose.test.yml
version: '3.8'
services:
test-db:
image: postgres:16
environment:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: apso_test
ports:
- "5433:5432"# Start test database
docker-compose -f docker-compose.test.yml up -d
# Run tests
TEST_DATABASE_URL=postgresql://test:test@localhost:5433/apso_test npm run test:e2eUsing In-Memory SQLite
For faster unit tests:
// test/setup.ts
TypeOrmModule.forRoot({
type: 'sqlite',
database: ':memory:',
autoLoadEntities: true,
synchronize: true,
});Mocking
Mock APSO Context
const mockContext = {
userId: 'user-123',
organizationId: 'org-456',
sessionId: 'session-789',
};
// Use in tests
await service.create(dto, mockContext);Mock External Services
// Mock auth service
const mockAuthService = {
validateToken: jest.fn().mockResolvedValue({ userId: 'user-123' }),
getSession: jest.fn().mockResolvedValue({ organizationId: 'org-456' }),
};
// Provide in test module
{
provide: AuthService,
useValue: mockAuthService,
}Coverage
Generate coverage reports:
npm run test:covView the HTML report at coverage/lcov-report/index.html.
Coverage Thresholds
Configure in jest.config.js:
module.exports = {
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
};CI Integration
# .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: test
POSTGRES_PASSWORD: test
POSTGRES_DB: apso_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run test:cov
env:
DATABASE_URL: postgresql://test:test@localhost:5432/apso_test
- run: npm run test:e2e
env:
DATABASE_URL: postgresql://test:test@localhost:5432/apso_testRelated
Last updated on