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

Testing (TypeScript)

APSO-generated NestJS projects include Jest for testing. This guide covers unit, integration, and E2E testing patterns.

Test Structure

my-api/ ├── src/ │ └── modules/ │ └── projects/ │ ├── projects.service.spec.ts # Unit tests │ └── projects.controller.spec.ts # Unit tests └── test/ ├── app.e2e-spec.ts # E2E tests └── jest-e2e.json # E2E config

Running Tests

# Run unit tests npm test # Run with coverage npm run test:cov # Run E2E tests npm run test:e2e # Watch mode npm run test:watch

Unit Testing Services

Test services in isolation with mocked dependencies:

// src/modules/projects/projects.service.spec.ts import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { ProjectsService } from './projects.service'; import { Project } from '../../entities/project.entity'; describe('ProjectsService', () => { let service: ProjectsService; let mockRepository: jest.Mocked<Repository<Project>>; const mockProject = { id: 'uuid-1', name: 'Test Project', organizationId: 'org-1', createdAt: new Date(), updatedAt: new Date(), }; beforeEach(async () => { mockRepository = { find: jest.fn(), findOne: jest.fn(), create: 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 projects for organization', async () => { mockRepository.find.mockResolvedValue([mockProject]); const result = await service.findAll( { limit: 10, offset: 0 }, { organizationId: 'org-1' } as any, ); expect(result).toEqual([mockProject]); expect(mockRepository.find).toHaveBeenCalledWith({ where: { organizationId: 'org-1' }, take: 10, skip: 0, }); }); }); describe('create', () => { it('should create a project', async () => { const createDto = { name: 'New Project' }; mockRepository.create.mockReturnValue(mockProject as any); mockRepository.save.mockResolvedValue(mockProject); const result = await service.create( createDto, { organizationId: 'org-1' } as any, ); expect(result).toEqual(mockProject); expect(mockRepository.create).toHaveBeenCalledWith({ ...createDto, organizationId: 'org-1', }); }); }); describe('findOne', () => { it('should return a project', async () => { mockRepository.findOne.mockResolvedValue(mockProject); const result = await service.findOne('uuid-1', { organizationId: 'org-1' } as any); expect(result).toEqual(mockProject); }); it('should throw if not found', async () => { mockRepository.findOne.mockResolvedValue(null); await expect( service.findOne('invalid', { organizationId: 'org-1' } as any), ).rejects.toThrow('Project not found'); }); }); });

Unit Testing Controllers

Test controllers with mocked services:

// src/modules/projects/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>; const mockUser = { id: 'user-1', organizationId: 'org-1', }; 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: 'Test' }], total: 1, }; mockService.findAll.mockResolvedValue(mockResult); const result = await controller.findAll( { limit: 10, offset: 0 }, mockUser as any, ); expect(result).toEqual(mockResult); expect(mockService.findAll).toHaveBeenCalledWith( { limit: 10, offset: 0 }, mockUser, ); }); }); describe('create', () => { it('should create and return a project', async () => { const dto = { name: 'New Project' }; const mockProject = { id: '1', ...dto }; mockService.create.mockResolvedValue(mockProject); const result = await controller.create(dto, mockUser as any); expect(result).toEqual(mockProject); }); }); });

E2E Testing

Test the full API with HTTP requests:

// test/projects.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 (e2e)', () => { let app: INestApplication; let authToken: string; beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleFixture.createNestApplication(); app.setGlobalPrefix('api/v1'); await app.init(); // Get auth token const loginRes = await request(app.getHttpServer()) .post('/api/v1/auth/login') .send({ email: 'test@example.com', password: 'password' }); authToken = loginRes.body.accessToken; }); afterAll(async () => { await app.close(); }); describe('GET /api/v1/projects', () => { it('should require authentication', () => { return request(app.getHttpServer()) .get('/api/v1/projects') .expect(401); }); it('should return projects', () => { 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 /api/v1/projects', () => { it('should create a project', () => { return request(app.getHttpServer()) .post('/api/v1/projects') .set('Authorization', `Bearer ${authToken}`) .send({ name: 'E2E Test Project' }) .expect(201) .expect((res) => { expect(res.body.name).toBe('E2E Test Project'); expect(res.body.id).toBeDefined(); }); }); it('should validate input', () => { return request(app.getHttpServer()) .post('/api/v1/projects') .set('Authorization', `Bearer ${authToken}`) .send({}) // Missing required name .expect(400); }); }); });

Test Database

Use a separate test database:

// test/setup.ts import { TypeOrmModule } from '@nestjs/typeorm'; export const TestDatabaseModule = TypeOrmModule.forRoot({ type: 'postgres', url: process.env.TEST_DATABASE_URL, autoLoadEntities: true, synchronize: true, // OK for test database });

Mocking Patterns

Mock Repository

const mockRepository = { find: jest.fn().mockResolvedValue([]), findOne: jest.fn().mockResolvedValue(null), create: jest.fn().mockImplementation((dto) => dto), save: jest.fn().mockImplementation((entity) => ({ id: 'uuid', ...entity })), delete: jest.fn().mockResolvedValue({ affected: 1 }), };

Mock External Services

const mockEmailService = { send: jest.fn().mockResolvedValue(true), }; const module = await Test.createTestingModule({ providers: [ MyService, { provide: EmailService, useValue: mockEmailService }, ], }).compile();

Code Coverage

Generate coverage reports:

npm run test:cov

Configure in jest.config.js:

module.exports = { coverageThreshold: { global: { branches: 80, functions: 80, lines: 80, statements: 80, }, }, collectCoverageFrom: [ 'src/**/*.ts', '!src/**/*.spec.ts', '!src/main.ts', ], };
Last updated on