Skip to Content
🚀 APSO is now in public beta. Get started →

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 config

Running 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:watch

Unit 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:e2e

Using 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:cov

View 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_test
Last updated on