Use when nestJS testing with unit tests, integration tests, and e2e tests. Use when building well-tested NestJS applications.
Provides comprehensive NestJS testing patterns for unit, integration, and e2e tests with mocked providers, repositories, and HTTP testing. Use when writing or debugging tests for NestJS controllers, services, guards, interceptors, or GraphQL resolvers.
/plugin marketplace add TheBushidoCollective/han/plugin install jutsu-markdown@hanThis skill is limited to using the following tools:
Master testing in NestJS for building reliable applications with comprehensive unit, integration, and end-to-end tests.
Creating and configuring test modules with TestingModule.
import { Test, TestingModule } from '@nestjs/testing';
import { UserService } from './user.service';
import { getRepositoryToken } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
describe('UserService', () => {
let service: UserService;
let module: TestingModule;
beforeEach(async () => {
module = await Test.createTestingModule({
providers: [
UserService,
{
provide: getRepositoryToken(User),
useValue: {
find: jest.fn(),
findOne: jest.fn(),
save: jest.fn(),
create: jest.fn(),
delete: jest.fn(),
},
},
],
}).compile();
service = module.get<UserService>(UserService);
});
afterEach(async () => {
await module.close();
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('should find all users', async () => {
const users = [{ id: 1, name: 'John' }];
jest.spyOn(service, 'findAll').mockResolvedValue(users);
const result = await service.findAll();
expect(result).toEqual(users);
expect(service.findAll).toHaveBeenCalled();
});
});
// Custom provider testing
describe('ConfigService', () => {
let service: ConfigService;
beforeEach(async () => {
const module = await Test.createTestingModule({
providers: [
{
provide: ConfigService,
useFactory: () => {
return new ConfigService('.env.test');
},
},
],
}).compile();
service = module.get<ConfigService>(ConfigService);
});
it('should load config from test environment', () => {
expect(service.get('NODE_ENV')).toBe('test');
});
});
Mocking services and testing request/response handling.
import { Test, TestingModule } from '@nestjs/testing';
import { UserController } from './user.controller';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { NotFoundException } from '@nestjs/common';
describe('UserController', () => {
let controller: UserController;
let service: UserService;
const mockUserService = {
findAll: jest.fn(),
findOne: jest.fn(),
create: jest.fn(),
update: jest.fn(),
remove: jest.fn(),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [UserController],
providers: [
{
provide: UserService,
useValue: mockUserService,
},
],
}).compile();
controller = module.get<UserController>(UserController);
service = module.get<UserService>(UserService);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('findAll', () => {
it('should return an array of users', async () => {
const users = [
{ id: 1, name: 'John', email: 'john@example.com' },
{ id: 2, name: 'Jane', email: 'jane@example.com' },
];
mockUserService.findAll.mockResolvedValue(users);
const result = await controller.findAll();
expect(result).toEqual(users);
expect(service.findAll).toHaveBeenCalledTimes(1);
});
it('should return empty array when no users', async () => {
mockUserService.findAll.mockResolvedValue([]);
const result = await controller.findAll();
expect(result).toEqual([]);
});
});
describe('findOne', () => {
it('should return a user by id', async () => {
const user = { id: 1, name: 'John', email: 'john@example.com' };
mockUserService.findOne.mockResolvedValue(user);
const result = await controller.findOne('1');
expect(result).toEqual(user);
expect(service.findOne).toHaveBeenCalledWith(1);
});
it('should throw NotFoundException when user not found', async () => {
mockUserService.findOne.mockRejectedValue(
new NotFoundException('User not found'),
);
await expect(controller.findOne('999')).rejects.toThrow(
NotFoundException,
);
});
});
describe('create', () => {
it('should create a new user', async () => {
const createUserDto: CreateUserDto = {
name: 'John',
email: 'john@example.com',
password: 'password123',
};
const createdUser = { id: 1, ...createUserDto };
mockUserService.create.mockResolvedValue(createdUser);
const result = await controller.create(createUserDto);
expect(result).toEqual(createdUser);
expect(service.create).toHaveBeenCalledWith(createUserDto);
});
});
describe('update', () => {
it('should update a user', async () => {
const updateDto = { name: 'Updated Name' };
const updatedUser = { id: 1, name: 'Updated Name', email: 'john@example.com' };
mockUserService.update.mockResolvedValue(updatedUser);
const result = await controller.update('1', updateDto);
expect(result).toEqual(updatedUser);
expect(service.update).toHaveBeenCalledWith(1, updateDto);
});
});
describe('remove', () => {
it('should delete a user', async () => {
mockUserService.remove.mockResolvedValue({ deleted: true });
const result = await controller.remove('1');
expect(result).toEqual({ deleted: true });
expect(service.remove).toHaveBeenCalledWith(1);
});
});
});
Mocking repositories and database operations.
import { Test, TestingModule } from '@nestjs/testing';
import { UserService } from './user.service';
import { Repository } from 'typeorm';
import { User } from './entities/user.entity';
import { getRepositoryToken } from '@nestjs/typeorm';
import { NotFoundException, ConflictException } from '@nestjs/common';
describe('UserService', () => {
let service: UserService;
let repository: Repository<User>;
const mockRepository = {
find: jest.fn(),
findOne: jest.fn(),
findOneBy: jest.fn(),
save: jest.fn(),
create: jest.fn(),
delete: jest.fn(),
update: jest.fn(),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
UserService,
{
provide: getRepositoryToken(User),
useValue: mockRepository,
},
],
}).compile();
service = module.get<UserService>(UserService);
repository = module.get<Repository<User>>(getRepositoryToken(User));
});
describe('findAll', () => {
it('should return an array of users', async () => {
const users = [{ id: 1, name: 'John', email: 'john@example.com' }];
mockRepository.find.mockResolvedValue(users);
const result = await service.findAll();
expect(result).toEqual(users);
expect(repository.find).toHaveBeenCalled();
});
});
describe('findOne', () => {
it('should return a user', async () => {
const user = { id: 1, name: 'John', email: 'john@example.com' };
mockRepository.findOneBy.mockResolvedValue(user);
const result = await service.findOne(1);
expect(result).toEqual(user);
expect(repository.findOneBy).toHaveBeenCalledWith({ id: 1 });
});
it('should throw NotFoundException when user not found', async () => {
mockRepository.findOneBy.mockResolvedValue(null);
await expect(service.findOne(999)).rejects.toThrow(NotFoundException);
});
});
describe('create', () => {
it('should create a new user', async () => {
const createDto = {
name: 'John',
email: 'john@example.com',
password: 'password123',
};
const user = { id: 1, ...createDto };
mockRepository.findOneBy.mockResolvedValue(null); // Email not taken
mockRepository.create.mockReturnValue(user);
mockRepository.save.mockResolvedValue(user);
const result = await service.create(createDto);
expect(result).toEqual(user);
expect(repository.create).toHaveBeenCalledWith(createDto);
expect(repository.save).toHaveBeenCalledWith(user);
});
it('should throw ConflictException when email exists', async () => {
const createDto = {
name: 'John',
email: 'john@example.com',
password: 'password123',
};
mockRepository.findOneBy.mockResolvedValue({ id: 1 }); // Email exists
await expect(service.create(createDto)).rejects.toThrow(
ConflictException,
);
});
});
describe('update', () => {
it('should update a user', async () => {
const updateDto = { name: 'Updated Name' };
const existingUser = { id: 1, name: 'John', email: 'john@example.com' };
const updatedUser = { ...existingUser, ...updateDto };
mockRepository.findOneBy.mockResolvedValue(existingUser);
mockRepository.save.mockResolvedValue(updatedUser);
const result = await service.update(1, updateDto);
expect(result).toEqual(updatedUser);
expect(repository.save).toHaveBeenCalled();
});
});
describe('remove', () => {
it('should delete a user', async () => {
const user = { id: 1, name: 'John', email: 'john@example.com' };
mockRepository.findOneBy.mockResolvedValue(user);
mockRepository.delete.mockResolvedValue({ affected: 1 });
await service.remove(1);
expect(repository.delete).toHaveBeenCalledWith(1);
});
it('should throw NotFoundException when deleting non-existent user', async () => {
mockRepository.findOneBy.mockResolvedValue(null);
await expect(service.remove(999)).rejects.toThrow(NotFoundException);
});
});
});
Factory providers and async providers.
import { Test, TestingModule } from '@nestjs/testing';
import { ConfigService } from '@nestjs/config';
import { DatabaseService } from './database.service';
describe('Factory Providers', () => {
let databaseService: DatabaseService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: 'DATABASE_CONNECTION',
useFactory: (config: ConfigService) => {
return {
host: config.get('DB_HOST'),
port: config.get('DB_PORT'),
database: config.get('DB_NAME'),
};
},
inject: [ConfigService],
},
{
provide: ConfigService,
useValue: {
get: jest.fn((key: string) => {
const config = {
DB_HOST: 'localhost',
DB_PORT: 5432,
DB_NAME: 'test_db',
};
return config[key];
}),
},
},
DatabaseService,
],
}).compile();
databaseService = module.get<DatabaseService>(DatabaseService);
});
it('should create database connection with correct config', () => {
const connection = databaseService.getConnection();
expect(connection.host).toBe('localhost');
expect(connection.port).toBe(5432);
expect(connection.database).toBe('test_db');
});
});
// Async provider testing
describe('Async Providers', () => {
let service: any;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
{
provide: 'ASYNC_CONNECTION',
useFactory: async () => {
await new Promise((resolve) => setTimeout(resolve, 100));
return { connected: true };
},
},
],
}).compile();
service = module.get('ASYNC_CONNECTION');
});
it('should resolve async provider', () => {
expect(service.connected).toBe(true);
});
});
Authentication and authorization guards.
import { Test, TestingModule } from '@nestjs/testing';
import { JwtAuthGuard } from './jwt-auth.guard';
import { JwtService } from '@nestjs/jwt';
import { ExecutionContext, UnauthorizedException } from '@nestjs/common';
describe('JwtAuthGuard', () => {
let guard: JwtAuthGuard;
let jwtService: JwtService;
const mockJwtService = {
verifyAsync: jest.fn(),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
JwtAuthGuard,
{
provide: JwtService,
useValue: mockJwtService,
},
],
}).compile();
guard = module.get<JwtAuthGuard>(JwtAuthGuard);
jwtService = module.get<JwtService>(JwtService);
});
it('should allow request with valid token', async () => {
const mockContext = createMockExecutionContext({
headers: { authorization: 'Bearer valid-token' },
});
mockJwtService.verifyAsync.mockResolvedValue({
userId: 1,
email: 'user@example.com',
});
const result = await guard.canActivate(mockContext);
expect(result).toBe(true);
expect(jwtService.verifyAsync).toHaveBeenCalledWith('valid-token', {
secret: expect.any(String),
});
});
it('should deny request without token', async () => {
const mockContext = createMockExecutionContext({
headers: {},
});
await expect(guard.canActivate(mockContext)).rejects.toThrow(
UnauthorizedException,
);
});
it('should deny request with invalid token', async () => {
const mockContext = createMockExecutionContext({
headers: { authorization: 'Bearer invalid-token' },
});
mockJwtService.verifyAsync.mockRejectedValue(new Error('Invalid token'));
await expect(guard.canActivate(mockContext)).rejects.toThrow(
UnauthorizedException,
);
});
});
// Helper function
function createMockExecutionContext(request: any): ExecutionContext {
return {
switchToHttp: () => ({
getRequest: () => request,
getResponse: () => ({}),
}),
getHandler: () => ({}),
getClass: () => ({}),
} as ExecutionContext;
}
// Testing RolesGuard
import { RolesGuard } from './roles.guard';
import { Reflector } from '@nestjs/core';
import { ForbiddenException } from '@nestjs/common';
describe('RolesGuard', () => {
let guard: RolesGuard;
let reflector: Reflector;
beforeEach(() => {
reflector = new Reflector();
guard = new RolesGuard(reflector);
});
it('should allow access when user has required role', () => {
jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(['admin']);
const mockContext = createMockExecutionContext({
user: { id: 1, roles: ['admin'] },
});
const result = guard.canActivate(mockContext);
expect(result).toBe(true);
});
it('should deny access when user lacks required role', () => {
jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(['admin']);
const mockContext = createMockExecutionContext({
user: { id: 1, roles: ['user'] },
});
expect(() => guard.canActivate(mockContext)).toThrow(ForbiddenException);
});
it('should allow access when no roles required', () => {
jest.spyOn(reflector, 'getAllAndOverride').mockReturnValue(undefined);
const mockContext = createMockExecutionContext({
user: { id: 1, roles: [] },
});
const result = guard.canActivate(mockContext);
expect(result).toBe(true);
});
});
Transformation and logging interceptors.
import { Test, TestingModule } from '@nestjs/testing';
import { TransformInterceptor } from './transform.interceptor';
import { ExecutionContext, CallHandler } from '@nestjs/common';
import { of } from 'rxjs';
describe('TransformInterceptor', () => {
let interceptor: TransformInterceptor;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [TransformInterceptor],
}).compile();
interceptor = module.get<TransformInterceptor>(TransformInterceptor);
});
it('should transform response data', (done) => {
const mockContext = {
switchToHttp: () => ({
getRequest: () => ({ url: '/test' }),
}),
} as ExecutionContext;
const mockCallHandler: CallHandler = {
handle: () => of({ name: 'Test', value: 123 }),
};
interceptor.intercept(mockContext, mockCallHandler).subscribe({
next: (result) => {
expect(result).toHaveProperty('data');
expect(result.data).toEqual({ name: 'Test', value: 123 });
expect(result).toHaveProperty('timestamp');
expect(result).toHaveProperty('path');
expect(result.path).toBe('/test');
done();
},
});
});
});
// Testing caching interceptor
import { CacheInterceptor } from './cache.interceptor';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
describe('CacheInterceptor', () => {
let interceptor: CacheInterceptor;
let cacheManager: any;
const mockCacheManager = {
get: jest.fn(),
set: jest.fn(),
};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
CacheInterceptor,
{
provide: CACHE_MANAGER,
useValue: mockCacheManager,
},
],
}).compile();
interceptor = module.get<CacheInterceptor>(CacheInterceptor);
cacheManager = module.get(CACHE_MANAGER);
});
it('should return cached data if available', async (done) => {
const cachedData = { cached: true };
mockCacheManager.get.mockResolvedValue(cachedData);
const mockContext = {
switchToHttp: () => ({
getRequest: () => ({ method: 'GET', url: '/test' }),
}),
} as ExecutionContext;
const mockCallHandler: CallHandler = {
handle: () => of({ fresh: true }),
};
const result$ = await interceptor.intercept(mockContext, mockCallHandler);
result$.subscribe({
next: (result) => {
expect(result).toEqual(cachedData);
expect(cacheManager.get).toHaveBeenCalledWith('GET:/test');
done();
},
});
});
it('should cache fresh data', (done) => {
const freshData = { fresh: true };
mockCacheManager.get.mockResolvedValue(null);
const mockContext = {
switchToHttp: () => ({
getRequest: () => ({ method: 'GET', url: '/test' }),
}),
} as ExecutionContext;
const mockCallHandler: CallHandler = {
handle: () => of(freshData),
};
interceptor.intercept(mockContext, mockCallHandler).then((result$) => {
result$.subscribe({
next: async (result) => {
expect(result).toEqual(freshData);
// Give time for cache to be set
await new Promise((resolve) => setTimeout(resolve, 100));
expect(cacheManager.set).toHaveBeenCalled();
done();
},
});
});
});
});
Validation and transformation pipes.
import { Test, TestingModule } from '@nestjs/testing';
import { ValidationPipe, BadRequestException } from '@nestjs/common';
import { ParseIntPipe } from '@nestjs/common';
import { ArgumentMetadata } from '@nestjs/common';
describe('ParseIntPipe', () => {
let pipe: ParseIntPipe;
beforeEach(() => {
pipe = new ParseIntPipe();
});
it('should parse valid number string', async () => {
const metadata: ArgumentMetadata = {
type: 'param',
metatype: Number,
data: 'id',
};
const result = await pipe.transform('123', metadata);
expect(result).toBe(123);
});
it('should throw error for invalid number string', async () => {
const metadata: ArgumentMetadata = {
type: 'param',
metatype: Number,
data: 'id',
};
await expect(pipe.transform('abc', metadata)).rejects.toThrow(
BadRequestException,
);
});
});
// Custom validation pipe testing
import { CustomValidationPipe } from './custom-validation.pipe';
import { IsString, IsEmail, MinLength } from 'class-validator';
class CreateUserDto {
@IsString()
@MinLength(3)
name: string;
@IsEmail()
email: string;
}
describe('CustomValidationPipe', () => {
let pipe: CustomValidationPipe;
beforeEach(() => {
pipe = new CustomValidationPipe();
});
it('should validate valid DTO', async () => {
const dto = {
name: 'John Doe',
email: 'john@example.com',
};
const metadata: ArgumentMetadata = {
type: 'body',
metatype: CreateUserDto,
};
const result = await pipe.transform(dto, metadata);
expect(result).toEqual(dto);
});
it('should throw error for invalid DTO', async () => {
const dto = {
name: 'Jo', // Too short
email: 'invalid-email',
};
const metadata: ArgumentMetadata = {
type: 'body',
metatype: CreateUserDto,
};
await expect(pipe.transform(dto, metadata)).rejects.toThrow(
BadRequestException,
);
});
});
Testing with supertest and real HTTP requests.
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication, ValidationPipe } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
import { getRepositoryToken } from '@nestjs/typeorm';
import { User } from '../src/users/entities/user.entity';
describe('UserController (e2e)', () => {
let app: INestApplication;
let userRepository: any;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
app.useGlobalPipes(new ValidationPipe());
userRepository = moduleFixture.get(getRepositoryToken(User));
await app.init();
});
afterAll(async () => {
await app.close();
});
beforeEach(async () => {
// Clean database before each test
await userRepository.query('DELETE FROM users');
});
describe('/users (POST)', () => {
it('should create a new user', () => {
return request(app.getHttpServer())
.post('/users')
.send({
name: 'John Doe',
email: 'john@example.com',
password: 'password123',
})
.expect(201)
.expect((res) => {
expect(res.body).toHaveProperty('id');
expect(res.body.name).toBe('John Doe');
expect(res.body.email).toBe('john@example.com');
expect(res.body).not.toHaveProperty('password');
});
});
it('should return 400 for invalid data', () => {
return request(app.getHttpServer())
.post('/users')
.send({
name: 'Jo', // Too short
email: 'invalid-email',
})
.expect(400);
});
});
describe('/users (GET)', () => {
it('should return all users', async () => {
// Seed data
await userRepository.save([
{ name: 'User 1', email: 'user1@example.com' },
{ name: 'User 2', email: 'user2@example.com' },
]);
return request(app.getHttpServer())
.get('/users')
.expect(200)
.expect((res) => {
expect(res.body).toHaveLength(2);
expect(res.body[0]).toHaveProperty('id');
expect(res.body[0]).toHaveProperty('name');
});
});
it('should return empty array when no users', () => {
return request(app.getHttpServer())
.get('/users')
.expect(200)
.expect([]);
});
});
describe('/users/:id (GET)', () => {
it('should return a user by id', async () => {
const user = await userRepository.save({
name: 'John Doe',
email: 'john@example.com',
});
return request(app.getHttpServer())
.get(`/users/${user.id}`)
.expect(200)
.expect((res) => {
expect(res.body.id).toBe(user.id);
expect(res.body.name).toBe('John Doe');
});
});
it('should return 404 for non-existent user', () => {
return request(app.getHttpServer()).get('/users/999').expect(404);
});
});
describe('/users/:id (PATCH)', () => {
it('should update a user', async () => {
const user = await userRepository.save({
name: 'John Doe',
email: 'john@example.com',
});
return request(app.getHttpServer())
.patch(`/users/${user.id}`)
.send({ name: 'Jane Doe' })
.expect(200)
.expect((res) => {
expect(res.body.name).toBe('Jane Doe');
});
});
});
describe('/users/:id (DELETE)', () => {
it('should delete a user', async () => {
const user = await userRepository.save({
name: 'John Doe',
email: 'john@example.com',
});
await request(app.getHttpServer())
.delete(`/users/${user.id}`)
.expect(200);
// Verify deletion
const deletedUser = await userRepository.findOne({ where: { id: user.id } });
expect(deletedUser).toBeNull();
});
});
});
In-memory, Docker, and test containers.
// In-memory SQLite for testing
import { Test, TestingModule } from '@nestjs/testing';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
describe('UserService with In-Memory DB', () => {
let module: TestingModule;
let service: UserService;
beforeAll(async () => {
module = await Test.createTestingModule({
imports: [
TypeOrmModule.forRoot({
type: 'sqlite',
database: ':memory:',
entities: [User],
synchronize: true,
dropSchema: true,
}),
TypeOrmModule.forFeature([User]),
],
providers: [UserService],
}).compile();
service = module.get<UserService>(UserService);
});
afterAll(async () => {
await module.close();
});
it('should create and retrieve a user', async () => {
const user = await service.create({
name: 'John',
email: 'john@example.com',
password: 'password123',
});
expect(user.id).toBeDefined();
const foundUser = await service.findOne(user.id);
expect(foundUser.name).toBe('John');
});
});
// Test with Docker container (using testcontainers)
import { GenericContainer, StartedTestContainer } from 'testcontainers';
describe('UserService with PostgreSQL Container', () => {
let container: StartedTestContainer;
let module: TestingModule;
beforeAll(async () => {
// Start PostgreSQL container
container = await new GenericContainer('postgres:15')
.withEnvironment({
POSTGRES_USER: 'test',
POSTGRES_PASSWORD: 'test',
POSTGRES_DB: 'testdb',
})
.withExposedPorts(5432)
.start();
const port = container.getMappedPort(5432);
module = await Test.createTestingModule({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port,
username: 'test',
password: 'test',
database: 'testdb',
entities: [User],
synchronize: true,
}),
TypeOrmModule.forFeature([User]),
],
providers: [UserService],
}).compile();
}, 60000);
afterAll(async () => {
await module.close();
await container.stop();
});
it('should work with real PostgreSQL', async () => {
const service = module.get<UserService>(UserService);
const user = await service.create({
name: 'John',
email: 'john@example.com',
password: 'password123',
});
expect(user.id).toBeDefined();
});
});
WebSocket gateway testing.
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import { io, Socket } from 'socket.io-client';
import { ChatGateway } from './chat.gateway';
describe('ChatGateway (e2e)', () => {
let app: INestApplication;
let clientSocket: Socket;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
providers: [ChatGateway],
}).compile();
app = moduleFixture.createNestApplication();
await app.listen(3001);
});
afterAll(async () => {
await app.close();
});
beforeEach((done) => {
clientSocket = io('http://localhost:3001');
clientSocket.on('connect', done);
});
afterEach(() => {
clientSocket.close();
});
it('should receive messages', (done) => {
clientSocket.emit('message', { text: 'Hello World' });
clientSocket.on('message', (data) => {
expect(data.text).toBe('Hello World');
done();
});
});
it('should handle multiple clients', (done) => {
const client2 = io('http://localhost:3001');
client2.on('connect', () => {
clientSocket.emit('message', { text: 'Broadcast' });
client2.on('message', (data) => {
expect(data.text).toBe('Broadcast');
client2.close();
done();
});
});
});
});
GraphQL testing with supertest.
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
describe('UserResolver (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: true,
}),
UserModule,
],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
afterAll(async () => {
await app.close();
});
it('should query users', () => {
return request(app.getHttpServer())
.post('/graphql')
.send({
query: `
query {
users {
id
name
email
}
}
`,
})
.expect(200)
.expect((res) => {
expect(res.body.data.users).toBeDefined();
expect(Array.isArray(res.body.data.users)).toBe(true);
});
});
it('should create a user', () => {
return request(app.getHttpServer())
.post('/graphql')
.send({
query: `
mutation {
createUser(createUserInput: {
name: "John Doe"
email: "john@example.com"
}) {
id
name
email
}
}
`,
})
.expect(200)
.expect((res) => {
expect(res.body.data.createUser).toHaveProperty('id');
expect(res.body.data.createUser.name).toBe('John Doe');
});
});
});
jest.mock and custom providers.
// Mock entire module
jest.mock('./user.service');
import { UserService } from './user.service';
describe('UserController with mocked service', () => {
let controller: UserController;
beforeEach(() => {
controller = new UserController(new UserService());
});
it('should use mocked service', async () => {
jest.spyOn(UserService.prototype, 'findAll').mockResolvedValue([]);
const result = await controller.findAll();
expect(result).toEqual([]);
});
});
// Partial mocking
const mockUserService = {
findAll: jest.fn(),
findOne: jest.fn(),
} as unknown as UserService;
// Mock external dependencies
import axios from 'axios';
jest.mock('axios');
const mockedAxios = axios as jest.Mocked<typeof axios>;
describe('ExternalApiService', () => {
it('should fetch data from external API', async () => {
mockedAxios.get.mockResolvedValue({ data: { result: 'success' } });
const service = new ExternalApiService();
const result = await service.fetchData();
expect(result).toEqual({ result: 'success' });
expect(mockedAxios.get).toHaveBeenCalledWith('https://api.example.com/data');
});
});
// Custom mock factory
function createMockRepository() {
return {
find: jest.fn(),
findOne: jest.fn(),
save: jest.fn(),
create: jest.fn((dto) => dto),
delete: jest.fn(),
};
}
Creating reusable test data.
// User factory
export class UserFactory {
static create(overrides?: Partial<User>): User {
return {
id: 1,
name: 'John Doe',
email: 'john@example.com',
password: 'hashed_password',
createdAt: new Date(),
updatedAt: new Date(),
...overrides,
};
}
static createMany(count: number, overrides?: Partial<User>): User[] {
return Array.from({ length: count }, (_, i) =>
this.create({ id: i + 1, ...overrides }),
);
}
}
// Usage in tests
describe('UserService', () => {
it('should find users', async () => {
const users = UserFactory.createMany(3);
mockRepository.find.mockResolvedValue(users);
const result = await service.findAll();
expect(result).toHaveLength(3);
});
});
// Builder pattern for complex entities
class UserBuilder {
private user: Partial<User> = {};
withName(name: string): this {
this.user.name = name;
return this;
}
withEmail(email: string): this {
this.user.email = email;
return this;
}
asAdmin(): this {
this.user.role = 'admin';
return this;
}
build(): User {
return {
id: 1,
name: 'John Doe',
email: 'john@example.com',
password: 'password',
role: 'user',
createdAt: new Date(),
updatedAt: new Date(),
...this.user,
} as User;
}
}
// Usage
const adminUser = new UserBuilder()
.withName('Admin User')
.withEmail('admin@example.com')
.asAdmin()
.build();
Testing configuration for coverage and automation.
// jest.config.js
module.exports = {
moduleFileExtensions: ['js', 'json', 'ts'],
rootDir: 'src',
testRegex: '.*\\.spec\\.ts$',
transform: {
'^.+\\.(t|j)s$': 'ts-jest',
},
collectCoverageFrom: [
'**/*.(t|j)s',
'!**/*.module.ts',
'!**/node_modules/**',
'!**/dist/**',
],
coverageDirectory: '../coverage',
testEnvironment: 'node',
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
};
// package.json scripts
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
}
}
// GitHub Actions CI
// .github/workflows/test.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: npm ci
- run: npm run test:cov
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
Use nestjs-testing when:
Master authentication and authorization patterns including JWT, OAuth2, session management, and RBAC to build secure, scalable access control systems. Use when implementing auth systems, securing APIs, or debugging security issues.