Skip to content
NestJS ns testing 4 min read

Mocking & Overriding Providers

Tests should exercise the unit you care about while everything else behaves predictably. NestJS makes this possible because the same dependency-injection container that wires your app at runtime is rebuilt by Test.createTestingModule, and its fluent override API lets you swap any provider, guard, interceptor, pipe, or filter for a fake. This page covers the override methods, Jest’s auto-mocking helpers, and typed mock libraries such as @golevelup/ts-jest so your mocks stay in sync with the real interfaces they replace.

Overriding providers

When you import a real feature module to get its full wiring, you rarely want every collaborator to be real. The .overrideProvider(token) chain replaces a single provider after the module definition but before .compile(). It accepts the same shapes as a custom provider: useValue, useClass, and useFactory.

import { Test, TestingModule } from '@nestjs/testing';
import { CatsModule } from './cats.module';
import { CatsRepository } from './cats.repository';
import { MailService } from '../mail/mail.service';

describe('CatsModule (mocked deps)', () => {
  let module: TestingModule;

  const repoMock = {
    findById: jest.fn(),
    findAll: jest.fn(),
  };

  beforeEach(async () => {
    module = await Test.createTestingModule({
      imports: [CatsModule],
    })
      .overrideProvider(CatsRepository)
      .useValue(repoMock)
      .overrideProvider(MailService)
      .useClass(FakeMailService) // a real class with no side effects
      .compile();
  });

  afterEach(() => jest.clearAllMocks());
});

The override targets the injection token, so it works for class tokens and string/symbol tokens alike. If a provider appears in several imported modules, a single override replaces it everywhere it is injected.

Overriding guards, interceptors, pipes, and filters

End-to-end and controller tests often fail because a real AuthGuard rejects the request, or a RolesGuard needs a populated user. Rather than forging valid JWTs, override the guard so it always allows the request. The corresponding methods are .overrideGuard(), .overrideInterceptor(), .overridePipe(), and .overrideFilter().

import { CanActivate } from '@nestjs/common';
import { AuthGuard } from '../auth/auth.guard';
import { LoggingInterceptor } from '../common/logging.interceptor';

const allowGuard: CanActivate = { canActivate: () => true };

const module = await Test.createTestingModule({
  imports: [CatsModule],
})
  .overrideGuard(AuthGuard)
  .useValue(allowGuard)
  .overrideInterceptor(LoggingInterceptor)
  .useValue({ intercept: (_ctx, next) => next.handle() })
  .compile();

Tip: an override applies regardless of where the enhancer is bound — controller-level @UseGuards(), method-level, or global via APP_GUARD. Overriding the class token catches all of them.

Auto-mocking with useMocker

Listing a mock for every dependency is tedious in large graphs. .useMocker() supplies a factory that Nest calls for any token it cannot otherwise resolve, letting you mock unspecified dependencies in bulk. Combine it with the MockFactory token to inspect what is being requested.

import { ModuleMocker, MockMetadata } from 'jest-mock';

const moduleMocker = new ModuleMocker(global);

const module = await Test.createTestingModule({
  providers: [CatsService],
})
  .useMocker((token) => {
    if (typeof token === 'function') {
      const meta = moduleMocker.getMetadata(token) as MockMetadata<any, any>;
      const Mock = moduleMocker.generateFromMetadata(meta);
      return new Mock();
    }
  })
  .compile();

const service = module.get(CatsService);

Every method on the auto-generated mock is a jest.fn() returning undefined, so you still program the cases you assert on.

Typed mocks with @golevelup/ts-jest

Hand-written jest.fn() objects drift from the real interface and lose type safety. @golevelup/ts-jest provides createMock<T>(), which returns a deeply mocked object where every method is already a typed Jest mock — including nested objects like an ExecutionContext.

npm install --save-dev @golevelup/ts-jest
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { ExecutionContext } from '@nestjs/common';
import { CatsRepository } from './cats.repository';

const repo: DeepMocked<CatsRepository> = createMock<CatsRepository>();

repo.findById.mockResolvedValue({ id: 1, name: 'Felix', age: 3 });

// Nested mocks work out of the box:
const ctx = createMock<ExecutionContext>();
ctx.switchToHttp().getRequest.mockReturnValue({ user: { id: 7 } });

Provide the typed mock to the module the same way as any other value:

const module = await Test.createTestingModule({
  providers: [
    CatsService,
    { provide: CatsRepository, useValue: repo },
  ],
}).compile();

Output:

 PASS  src/cats/cats.service.spec.ts
  CatsService (typed mocks)
    ✓ returns the cat when it exists (2 ms)
    ✓ short-circuits when repository is empty (1 ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Time:        0.974 s

Choosing a mocking approach

ApproachAPIBest for
Static value{ provide, useValue }Small, explicit stubs
Override on imported module.overrideProvider().useValue()Replacing one provider in a real module
Enhancer override.overrideGuard() / .overrideInterceptor()Bypassing auth/logging in e2e tests
Bulk auto-mock.useMocker()Large graphs with many leaf dependencies
Typed deep mockcreateMock<T>()Type-safe mocks, nested objects, contexts

Warning: overrides resolve against the injection token, not the variable name. If two distinct classes share a string token, an override hits both — prefer unique class or symbol tokens to avoid surprises.

Best practices

  • Override against the token your code actually injects; a mismatched token silently leaves the real provider in place.
  • Prefer createMock<T>() or jest.Mocked<T> over loose objects so interface changes surface as compile errors.
  • Use .overrideGuard() to neutralize auth in tests instead of constructing valid credentials or tokens.
  • Reach for .useMocker() only when explicit mocks become noise; keep critical collaborators explicit so intent stays readable.
  • Reset mock state between tests with jest.clearAllMocks() or clearMocks: true to keep cases order-independent.
  • Assert on mock interactions (toHaveBeenCalledWith) so an override does not hide a broken call you meant to verify.
Last updated June 14, 2026
Was this helpful?