Skip to Content
Apso is in public beta. Get started

Test TypeScript services

The TypeScript template uses Jest. Generated entity directories include service and controller specs, and the repository supports separate end-to-end and integration configurations.

Commands

npm test # Unit tests npm run test:watch # Unit tests in watch mode npm run test:cov # Coverage report npm run test:e2e # End-to-end suite npm run test:integration # Integration suite npm run build # Type-check and compile the service

Run npm test and npm run build after apso generate. Generated type changes can affect custom modules even when a generated smoke test still passes.

Generated tests

For an entity named Project, generated tests live beside the generated files:

src/autogen/Project/ |-- Project.service.spec.ts |-- Project.controller.spec.ts |-- Project.service.ts `-- Project.controller.ts

The service spec supplies a mocked TypeORM repository. The controller spec supplies a mocked generated service. These tests confirm that NestJS can construct the generated classes and that controller methods delegate to the service.

Generated specs can be replaced when you regenerate. Keep product-specific tests with your custom modules or under test/.

Test a custom workflow

This unit test covers the archive workflow from the extensions guide. It verifies both the state change and the missing-record behavior.

src/extensions/project-workflow.service.spec.ts
import { NotFoundException } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { Project } from '../autogen/Project/Project.entity'; import { ProjectWorkflowService } from './project-workflow.service'; describe('ProjectWorkflowService', () => { const projects = { findOne: jest.fn(), save: jest.fn(), }; let service: ProjectWorkflowService; beforeEach(async () => { jest.resetAllMocks(); const moduleRef = await Test.createTestingModule({ providers: [ ProjectWorkflowService, { provide: getRepositoryToken(Project), useValue: projects, }, ], }).compile(); service = moduleRef.get(ProjectWorkflowService); }); it('archives an existing project', async () => { const project = { id: 'project-1', name: 'Docs', status: 'Active' }; projects.findOne.mockResolvedValue(project); projects.save.mockImplementation(async (value) => value); await expect(service.archive('project-1')).resolves.toMatchObject({ id: 'project-1', status: 'Archived', }); expect(projects.save).toHaveBeenCalledWith(project); }); it('rejects an unknown project', async () => { projects.findOne.mockResolvedValue(null); await expect(service.archive('missing')).rejects.toBeInstanceOf( NotFoundException, ); }); });

Test the HTTP contract

Use supertest when behavior depends on NestJS routing, guards, interceptors, or database wiring.

test/projects.e2e-spec.ts
import { INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; import request from 'supertest'; import { AppModule } from '../src/app.module.rest'; import { configureNestApp } from '../src/config/nest-app.config'; describe('Projects API', () => { let app: INestApplication; beforeAll(async () => { const moduleRef = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = moduleRef.createNestApplication(); await configureNestApp(app); await app.init(); }); afterAll(() => app.close()); it('lists projects', async () => { const response = await request(app.getHttpServer()) .get('/Projects') .expect(200); expect(response.body).toHaveProperty('data'); }); });

Use an isolated test database for an end-to-end suite. Never run a test that truncates or synchronizes schema against a shared development or production database.

Cover the risks that matter

For a generated CRUD resource, add tests for the behavior your product depends on:

  • Required and allowed field values
  • Authentication failure and success
  • Cross-tenant access denial
  • Relationship creation and deletion
  • Pagination and filters used by the client
  • Custom workflows and third-party failures
  • Migration behavior for existing records

Test one complete client journey after deployment as well. A useful minimum is create, read, update, and delete through the same API key and route shape the application uses.

Last updated on