Testing Overview
A NestJS application is built from small, decoupled providers wired together by dependency injection, which makes it unusually pleasant to test. The framework ships with first-class testing support out of the box: every project scaffolded by the CLI comes pre-configured with Jest, and the @nestjs/testing package lets you spin up a real or partial DI container in memory. This page surveys the testing pyramid for Nest, explains the Jest setup the CLI gives you, and shows how to decide between isolating a single unit and bootstrapping the whole application.
The testing pyramid for Nest
Tests trade speed for confidence. The closer a test runs to production behavior, the more it costs to run and maintain, so a healthy suite is shaped like a pyramid: many fast unit tests at the base, fewer integration tests in the middle, and a thin layer of slow end-to-end tests at the top.
| Layer | Scope | DI container | Speed | What it proves |
|---|---|---|---|---|
| Unit | One class, dependencies mocked | None or minimal | Fastest | Business logic in isolation |
| Integration | A module + real collaborators | Partial (Test.createTestingModule) | Medium | Providers wire together correctly |
| E2E | The whole app over HTTP | Full (NestFactory-equivalent) | Slowest | Routes, guards, pipes end to end |
In Nest the boundary between unit and integration tests is decided entirely by what you put in the testing module’s providers array. Supply mocks and you have a unit test; supply the real collaborators (and a real database) and the same harness becomes an integration test.
Jest configuration from the CLI
When you generate a project with nest new, the CLI installs Jest, ts-jest, supertest, and the matching @types packages, then writes the configuration into package.json and a separate test/jest-e2e.json. You usually never touch these files.
nest new my-api
The relevant block in package.json looks like this:
{
"jest": {
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": { "^.+\\.(t|j)s$": "ts-jest" },
"collectCoverageFrom": ["**/*.(t|j)s"],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
Unit and integration specs use the *.spec.ts suffix and live next to the code they test under src/. End-to-end specs use *.e2e-spec.ts and live in the top-level test/ directory, driven by the separate jest-e2e.json config. The generated scripts cover every workflow:
npm run test # run all *.spec.ts unit/integration tests
npm run test:watch # re-run on file change
npm run test:cov # produce a coverage report
npm run test:e2e # run *.e2e-spec.ts against a booted app
Output:
PASS src/cats/cats.service.spec.ts
CatsService
✓ should return an array of cats (4 ms)
✓ should throw when a cat is missing (2 ms)
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Time: 1.842 s
The @nestjs/testing package
The heart of Nest testing is Test.createTestingModule(). It accepts the same metadata object as the @Module() decorator — imports, controllers, providers — and returns a builder. Calling compile() instantiates a real DI container so you can pull out fully-wired instances with module.get().
// src/cats/cats.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { CatsService } from './cats.service';
import { CatsRepository } from './cats.repository';
describe('CatsService', () => {
let service: CatsService;
let repo: { findAll: jest.Mock };
beforeEach(async () => {
repo = { findAll: jest.fn().mockResolvedValue([{ id: 1, name: 'Milo' }]) };
const module: TestingModule = await Test.createTestingModule({
providers: [
CatsService,
{ provide: CatsRepository, useValue: repo },
],
}).compile();
service = module.get<CatsService>(CatsService);
});
it('returns cats from the repository', async () => {
await expect(service.findAll()).resolves.toEqual([{ id: 1, name: 'Milo' }]);
expect(repo.findAll).toHaveBeenCalledTimes(1);
});
});
Because the container is real, constructor injection works exactly as it does at runtime — you simply override individual providers with useValue, useClass, or useFactory.
Use
overrideProvider(Token).useValue(mock)on the builder when you import a whole module but want to swap just one dependency, rather than re-declaring every provider yourself.
Isolating a unit versus testing the whole app
A pure unit test does not need the DI container at all — you can new the class up and pass mocks directly to its constructor, which is the fastest possible test. Reach for Test.createTestingModule when you want Nest to resolve a small graph of providers for you, and reach for the full INestApplication (via app.init()) only for e2e coverage of the HTTP layer.
// no DI container at all — the leanest unit test
import { CatsService } from './cats.service';
it('formats a greeting', () => {
const service = new CatsService({ findAll: jest.fn() } as any);
expect(service.greeting()).toBe('Meow');
});
For e2e tests you compile the testing module and then create an actual application instance, wrapping it with supertest to fire real HTTP requests through the entire pipe, guard, and interceptor stack.
// test/cats.e2e-spec.ts
import { Test } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
describe('Cats (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleRef.createNestApplication();
await app.init();
});
afterAll(async () => app.close());
it('GET /cats', () => {
return request(app.getHttpServer())
.get('/cats')
.expect(200);
});
});
Best Practices
- Keep the pyramid shape: write many unit tests, fewer integration tests, and only a handful of e2e tests for critical user journeys.
- Co-locate
*.spec.tsfiles with the code they cover and reservetest/*.e2e-spec.tsfor full-app HTTP tests. - Prefer plain constructor instantiation for logic-only unit tests; bring in
Test.createTestingModuleonly when you actually need DI. - Mock at the boundary — databases, HTTP clients, queues — so unit tests stay deterministic and fast.
- Always call
app.close()inafterAllfor e2e tests to release ports, connections, and timers. - Run
npm run test:covin CI and watch the trend rather than chasing a single magic percentage.