Skip to content
NestJS ns testing 5 min read

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.

LayerScopeDI containerSpeedWhat it proves
UnitOne class, dependencies mockedNone or minimalFastestBusiness logic in isolation
IntegrationA module + real collaboratorsPartial (Test.createTestingModule)MediumProviders wire together correctly
E2EThe whole app over HTTPFull (NestFactory-equivalent)SlowestRoutes, 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.ts files with the code they cover and reserve test/*.e2e-spec.ts for full-app HTTP tests.
  • Prefer plain constructor instantiation for logic-only unit tests; bring in Test.createTestingModule only when you actually need DI.
  • Mock at the boundary — databases, HTTP clients, queues — so unit tests stay deterministic and fast.
  • Always call app.close() in afterAll for e2e tests to release ports, connections, and timers.
  • Run npm run test:cov in CI and watch the trend rather than chasing a single magic percentage.
Last updated June 14, 2026
Was this helpful?