Unit Testing with the Testing Module
Use Test.createTestingModule to build an isolated DI container and inject mocks with custom providers.
Nest's dependency injection is what makes it so testable. In a unit test you spin up a miniature DI container containing just the class under test, with every dependency replaced by a mock. No HTTP, no database, microsecond-fast.
Test.createTestingModule
It mirrors @Module(): list your real provider, then override each of its dependencies with a useValue mock. Call .compile() and pull the instance out with module.get().
const module = await Test.createTestingModule({
providers: [
UsersService,
{ provide: UsersRepository, useValue: mockRepo },
],
}).compile();Mock at the token, not the import
Because you inject by token, you swap the dependency by re-registering the same token with a fake. The class under test never knows the difference - it just receives the mock the container hands it. This is why constructor injection beats importing singletons directly.
overrideProvider for bigger graphs
When you import a whole module, .overrideProvider(Token).useValue(mock) surgically replaces one provider while keeping the rest real. Handy for testing a service with its actual collaborators but a fake database.
Assert on behavior
Verify what the service did: the value it returned and the calls it made on its mocks (expect(mockRepo.save).toHaveBeenCalledWith(...)). Testing behavior rather than implementation keeps tests green through refactors.
Example
import { Test, TestingModule } from '@nestjs/testing';
import { NotFoundException } from '@nestjs/common';
interface User {
id: string;
email: string;
}
class UsersRepository {
findById(_id: string): Promise<User | null> {
throw new Error('not implemented');
}
}
class UsersService {
constructor(private readonly repo: UsersRepository) {}
async findOne(id: string): Promise<User> {
const user = await this.repo.findById(id);
if (!user) throw new NotFoundException(id);
return user;
}
}
describe('UsersService', () => {
let service: UsersService;
const repo = { findById: jest.fn() };
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UsersService,
// same token, fake implementation
{ provide: UsersRepository, useValue: repo },
],
}).compile();
service = module.get(UsersService);
jest.resetAllMocks();
});
it('returns the user when found', async () => {
repo.findById.mockResolvedValue({ id: '1', email: '[email protected]' });
await expect(service.findOne('1')).resolves.toEqual({
id: '1',
email: '[email protected]',
});
expect(repo.findById).toHaveBeenCalledWith('1');
});
it('throws NotFound when missing', async () => {
repo.findById.mockResolvedValue(null);
await expect(service.findOne('x')).rejects.toBeInstanceOf(
NotFoundException,
);
});
});When to use it
- A developer uses Test.createTestingModule to unit-test OrdersService with a mock repository so tests run without a database connection.
- A team injects mock implementations for all external dependencies (email, payment) so unit tests are fast, deterministic, and runnable in CI without secrets.
- An engineer uses jest.spyOn to verify that a specific service method was called with the expected arguments inside a controller unit test.
More examples
Service unit test with mock repo
Creates an isolated testing module with a mock TypeORM repository so UsersService methods can be tested without a real database.
import { Test } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { UsersService } from './users.service';
import { User } from './user.entity';
const mockRepo = {
find: jest.fn(() => [{ id: 1, name: 'Alice' }]),
findOneBy: jest.fn((q: any) => ({ id: q.id, name: 'Alice' })),
};
const module = await Test.createTestingModule({
providers: [
UsersService,
{ provide: getRepositoryToken(User), useValue: mockRepo },
],
}).compile();
const service = module.get(UsersService);
expect(await service.findAll()).toHaveLength(1);Verify method calls with spyOn
Uses jest.spyOn to assert that the controller delegates to service.findAll exactly once, verifying the delegation contract.
describe('UsersController', () => {
let controller: UsersController;
let service: UsersService;
beforeEach(async () => {
const module = await Test.createTestingModule({
controllers: [UsersController],
providers: [{ provide: UsersService, useValue: { findAll: jest.fn(() => []) } }],
}).compile();
controller = module.get(UsersController);
service = module.get(UsersService);
});
it('calls service.findAll', async () => {
const spy = jest.spyOn(service, 'findAll');
await controller.findAll();
expect(spy).toHaveBeenCalledTimes(1);
});
});Test guard canActivate in isolation
Unit tests a guard in complete isolation by constructing a fake ExecutionContext object, no testing module needed.
import { ExecutionContext } from '@nestjs/common';
import { AuthGuard } from './auth.guard';
const guard = new AuthGuard();
const createContext = (headers: Record<string, string>) => ({
switchToHttp: () => ({
getRequest: () => ({ headers }),
}),
} as unknown as ExecutionContext);
it('allows request with valid API key', () => {
process.env.API_KEY = 'secret';
expect(guard.canActivate(createContext({ 'x-api-key': 'secret' }))).toBe(true);
});
it('rejects request without API key', () => {
expect(guard.canActivate(createContext({}))).toBe(false);
});
Discussion