Suggest an editImprove this articleRefine the answer for “How to test a NestJS application?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**NestJS testing** uses `@nestjs/testing` to create isolated DI containers for unit tests and boots the full app for e2e tests via Supertest. ```typescript const module = await Test.createTestingModule({ providers: [ UsersService, { provide: getRepositoryToken(User), useValue: mockRepo } ], }).compile(); const service = module.get(UsersService); ``` **Key:** mock only what the tested provider needs; always call `await app.init()` before the first e2e request.Shown above the full answer for quick recall.Answer (EN)Image**NestJS testing** uses `@nestjs/testing` to build isolated DI containers for unit tests and boots the full app for e2e tests via Supertest. ## Theory ### TL;DR - Unit tests create a lightweight module with `Test.createTestingModule()`, swap real deps with `jest.fn()` mocks, and run in milliseconds - E2e tests boot the full Express/Fastify server, send real HTTP requests through Supertest, and verify end-to-end flows - Override guards, pipes, and interceptors with `.overrideGuard()`, `.overridePipe()`, `.overrideInterceptor()` - Unit test pure service logic; e2e test auth flows, validation, and multi-step user journeys - Always `await app.init()` before any e2e request, or you get ECONNREFUSED ### Quick example ```typescript // Unit test: mock the TypeORM repo, test service logic in isolation import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { UsersService } from './users.service'; import { User } from './entities/user.entity'; describe('UsersService', () => { let service: UsersService; const mockRepo = { find: jest.fn().mockResolvedValue([{ id: 1, name: 'Alice' }]), findOneBy: jest.fn(), }; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ UsersService, { provide: getRepositoryToken(User), useValue: mockRepo }, ], }).compile(); service = module.get<UsersService>(UsersService); }); it('returns all users', async () => { const result = await service.findAll(); expect(result).toEqual([{ id: 1, name: 'Alice' }]); }); }); // ✓ returns all users (4ms) ``` `Test.createTestingModule()` builds a DI container that mirrors your real module but skips the HTTP server entirely. The mock repository replaces the real TypeORM one, so no database connection is needed. ### Unit tests vs e2e tests Unit tests isolate one provider. You override only the dependencies that provider needs, nothing else. The test container resolves providers via Nest's reflect-metadata scanner, exactly like production, but without any I/O. A well-written unit test runs in under 50ms. E2e tests call `module.createNestApplication()`, which spins up a real Express or Fastify server. Supertest binds to that server and sends actual HTTP requests. Guards, pipes, interceptors, and middleware all run. This is the only way to test validation errors, auth flows, or anything that happens at the HTTP layer. ### When to use each - Service with DB queries: unit test with a mocked repository - Controller with validation: unit test with a mocked service and a real `ValidationPipe` - Login flow (POST /auth → JWT → GET /profile): e2e test - Guard logic in isolation: unit test with a mocked `ExecutionContext` - Full user journey with real DB writes: e2e test with a test database If the test needs a real network call or a real database, make it an e2e test. Otherwise, keep it a unit test. ### How the module system works `Test.createTestingModule()` creates a lightweight DI container. It scans providers using Nest's [reflect-metadata decorators](/questions/nestjs-decorators), resolves injection tokens, and wires everything together. The key difference from `NestFactory.create()` is that it never starts an HTTP listener. When you provide `{ provide: getRepositoryToken(User), useValue: mockRepo }`, Nest replaces every injection point for that token with your mock. This works because TypeORM's `getRepositoryToken()` returns a predictable injection token string. The mechanism is the same as standard [NestJS dependency injection](/questions/nestjs-dependency-injection), with tokens mapping to providers at compile time. For e2e tests, `createNestApplication()` runs the same bootstrap logic as your `main.ts`. If you use `app.useGlobalPipes(new ValidationPipe())` in production, you need to add it in your test setup too. Skip this and your e2e validation tests will pass when they should fail. ### Overriding guards, pipes, and interceptors ```typescript const module = await Test.createTestingModule({ controllers: [UsersController], providers: [UsersService], }) .overrideGuard(JwtAuthGuard) .useValue({ canActivate: () => true }) .overridePipe(ParseIntPipe) .useValue({ transform: (val) => parseInt(val) }) .compile(); ``` `.overrideGuard()` targets route-level guards after DI resolution. It differs from `overrideProvider()`, which swaps any injectable by token. Use `overrideGuard` for guards, `overrideInterceptor` for interceptors, and `overrideProvider` for services, repositories, or config values. ### Common mistakes **Forgetting `await app.init()`** ```typescript // Wrong - server is not listening yet app = moduleFixture.createNestApplication(); request(app.getHttpServer()).get('/users'); // ECONNREFUSED // Correct await app.init(); request(app.getHttpServer()).get('/users'); // 200 ``` **Skipping global pipes in e2e setup.** If your app uses `ValidationPipe` globally, add it in `beforeAll`: ```typescript app.useGlobalPipes(new ValidationPipe({ whitelist: true })); await app.init(); ``` Without this, POST requests with invalid bodies return 201 instead of 400. Tests pass, but production rejects the same input. **Shared test database without cleanup.** Tests that write to a shared database leave data behind. The next test finds unexpected rows and fails. Fix: truncate tables in `beforeEach`, or use a separate schema per test run. This one bites every team eventually, usually right before a release. **Mocking the entire module instead of specific providers:** ```typescript // Wrong - loses DI context, module.get() fails jest.mock('./users.service'); // Correct - mock only what the tested unit needs { provide: UsersService, useValue: { findAll: jest.fn() } } ``` **Testing REQUEST-scoped providers in unit tests.** REQUEST-scoped providers recreate on every HTTP call. In unit tests there is no HTTP context, so `module.get()` on a REQUEST-scoped provider fails. Use e2e tests for those, or replace them with a DEFAULT-scoped mock via `overrideProvider`. NestJS v10 added `overrideScope` for more precise control here. ### Real-world usage - NestJS CLI generates `*.spec.ts` files with `@nestjs/testing` setup via `nest new project` - Prisma projects mock `PrismaService` in unit tests, use a `$transaction` spy for write flows - BullMQ queues: `overrideProvider(Queue).useValue(mockQueue)` tests processors without Redis - GraphQL e2e: import `GqlModule.forRoot` and send raw query strings through Supertest - Stripe webhooks: mock `stripe.webhookConstructEvent()` to avoid real charges in CI ### Follow-up questions **Q:** How do you mock a dynamic provider like `ConfigService` in NestJS 10+? **A:** Use `useFactory` with injection: `{ provide: 'API_KEY', useFactory: (config: ConfigService) => config.get('KEY'), inject: [ConfigService] }`. Or override `ConfigService` directly with `useValue: { get: jest.fn().mockReturnValue('test-key') }`. **Q:** What is the difference between `overrideProvider` and `overrideGuard`? **A:** `overrideProvider` swaps any injectable by its injection token, including services and repositories. `overrideGuard` targets guards after DI resolution and can replace route-bound guards that do not have a straightforward token. **Q:** How do you test a microservice with Redis transport without a real Redis instance? **A:** Mock the client entirely: `overrideProvider(getClientToken()).useValue({ send: jest.fn() })`. The goal is to never require a real broker connection in unit or CI tests. **Q:** Why does an e2e test pass locally but fail in CI? **A:** Most often it is a missing `globalSetup` for DB schema sync, a different `DATABASE_URL` env var, or a race condition in `beforeAll`. Add explicit schema migration in `globalSetup` and verify all env vars in your CI config. **Q:** How does REQUEST scope affect mocking in unit tests, and how do you handle it? **A:** REQUEST-scoped providers recreate on each HTTP request. In a unit test there is no request, so Nest cannot resolve them normally. You either test them in e2e where a real HTTP request provides the context, or replace them with a DEFAULT-scoped mock via `overrideProvider`. NestJS v10 introduced `overrideScope` to handle this case without switching to e2e. ## Examples ### Basic service unit test with NotFoundException ```typescript // users/users.service.spec.ts import { Test, TestingModule } from '@nestjs/testing'; import { getRepositoryToken } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { UsersService } from './users.service'; import { User } from './entities/user.entity'; import { NotFoundException } from '@nestjs/common'; describe('UsersService', () => { let service: UsersService; let repo: jest.Mocked<Repository<User>>; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ UsersService, { provide: getRepositoryToken(User), useValue: { findOneBy: jest.fn(), create: jest.fn(), save: jest.fn(), }, }, ], }).compile(); service = module.get<UsersService>(UsersService); repo = module.get(getRepositoryToken(User)); }); it('returns a user when found', async () => { const user = { id: 1, name: 'Alice', email: 'alice@test.com' } as User; repo.findOneBy.mockResolvedValue(user); const result = await service.findOne(1); expect(result).toEqual(user); expect(repo.findOneBy).toHaveBeenCalledWith({ id: 1 }); }); it('throws NotFoundException when user does not exist', async () => { repo.findOneBy.mockResolvedValue(null); await expect(service.findOne(99)).rejects.toThrow(NotFoundException); }); }); ``` `repo.findOneBy.mockResolvedValue(null)` simulates a missing record. The test verifies the service throws `NotFoundException`, not just returns null. That is the contract the rest of the codebase depends on. ### E2e test with auth flow and ValidationPipe ```typescript // test/auth.e2e-spec.ts import { Test } from '@nestjs/testing'; import { INestApplication, ValidationPipe } from '@nestjs/common'; import * as request from 'supertest'; import { AppModule } from '../src/app.module'; describe('Auth (e2e)', () => { let app: INestApplication; beforeAll(async () => { const module = await Test.createTestingModule({ imports: [AppModule], }).compile(); app = module.createNestApplication(); // Mirror your main.ts setup exactly app.useGlobalPipes(new ValidationPipe({ whitelist: true })); await app.init(); }); afterAll(async () => await app.close()); it('POST /auth/register returns 201 with access_token', async () => { const res = await request(app.getHttpServer()) .post('/auth/register') .send({ email: 'test@test.com', password: 'password123' }); expect(res.status).toBe(201); expect(res.body.access_token).toBeDefined(); }); it('POST /auth/register returns 400 with invalid email', async () => { const res = await request(app.getHttpServer()) .post('/auth/register') .send({ email: 'not-an-email', password: 'password123' }); expect(res.status).toBe(400); }); }); // ✓ POST /auth/register returns 201 with access_token (45ms) ``` `afterAll(async () => await app.close())` is not optional. Without it, Jest hangs after the suite finishes because the HTTP server is still listening. Every e2e file needs this. ### Controller unit test with a mocked guard ```typescript // users/users.controller.spec.ts import { Test } from '@nestjs/testing'; import { UsersController } from './users.controller'; import { UsersService } from './users.service'; import { JwtAuthGuard } from '../auth/jwt-auth.guard'; describe('UsersController', () => { let controller: UsersController; let service: jest.Mocked<UsersService>; beforeEach(async () => { const module = await Test.createTestingModule({ controllers: [UsersController], providers: [ { provide: UsersService, useValue: { findAll: jest.fn().mockResolvedValue([{ id: 1, name: 'Alice' }]), findOne: jest.fn(), }, }, ], }) .overrideGuard(JwtAuthGuard) .useValue({ canActivate: () => true }) .compile(); controller = module.get<UsersController>(UsersController); service = module.get(UsersService); }); it('calls service.findAll and returns its result', async () => { const result = await controller.findAll(); expect(result).toEqual([{ id: 1, name: 'Alice' }]); expect(service.findAll).toHaveBeenCalled(); }); }); ``` Without `.overrideGuard(JwtAuthGuard)`, the guard runs and throws because there is no JWT token in a unit test. The mock `{ canActivate: () => true }` bypasses auth entirely so you can focus on what the controller actually does with the service response.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.