Як протестувати додаток NestJS?
Тестування додатків NestJS
NestJS за замовчуванням використовує Jest і надає пакет @nestjs/testing для створення ізольованих тестових модулів. Фреймворк підтримує як модульні тести (ізольовані тести сервісів/контролерів), так і тести кінцевих точок (e2e) (повний стек HTTP).
Модульне тестування сервісу
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: {
find: jest.fn(),
findOneBy: jest.fn(),
create: jest.fn(),
save: jest.fn(),
delete: jest.fn(),
},
},
],
}).compile();
service = module.get<UsersService>(UsersService);
repo = module.get(getRepositoryToken(User));
});
describe('findOne()', () => {
it('повинен повернути користувача, коли знайдено', async () => {
const user: 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('повинен кинути NotFoundException, коли не знайдено', async () => {
repo.findOneBy.mockResolvedValue(null);
await expect(service.findOne(99)).rejects.toThrow(NotFoundException);
});
});
describe('create()', () => {
it('повинен створити і повернути користувача', async () => {
const dto = { name: 'Bob', email: 'bob@test.com' };
const user = { id: 2, ...dto } as User;
repo.create.mockReturnValue(user);
repo.save.mockResolvedValue(user);
const result = await service.create(dto);
expect(result).toEqual(user);
});
});
});Модульне тестування контролера
typescript
// users/users.controller.spec.ts
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([]),
findOne: jest.fn(),
create: jest.fn(),
},
},
],
}).compile();
controller = module.get<UsersController>(UsersController);
service = module.get(UsersService);
});
it('повинен повернути масив з findAll()', async () => {
const result = await controller.findAll();
expect(result).toEqual([]);
expect(service.findAll).toHaveBeenCalled();
});
});E2E Тестування
typescript
// test/users.e2e-spec.ts
import * as request from 'supertest';
describe('UsersController (e2e)', () => {
let app: INestApplication;
beforeAll(async () => {
const moduleFixture = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
await app.init();
});
afterAll(async () => await app.close());
it('GET /users → 200', () => {
return request(app.getHttpServer())
.get('/users')
.expect(200)
.expect(res => {
expect(Array.isArray(res.body)).toBe(true);
});
});
it('POST /users → 201 з дійсним тілом', () => {
return request(app.getHttpServer())
.post('/users')
.send({ name: 'Alice', email: 'alice@test.com' })
.expect(201);
});
it('POST /users → 400 з недійсним тілом', () => {
return request(app.getHttpServer())
.post('/users')
.send({ name: 'A' }) // занадто коротко, немає email
.expect(400);
});
});Тестування охоронців
typescript
// Переопределити охоронця в тесті
const module = await Test.createTestingModule({
controllers: [UsersController],
providers: [UsersService],
})
.overrideGuard(JwtAuthGuard)
.useValue({ canActivate: () => true }) // завжди дозволяти
.compile();Резюме
Тестування NestJS використовує Test.createTestingModule() для побудови ізольованих контекстів модулів. Для модульних тестів підробляйте залежності за допомогою jest.fn(). Для e2e тестів створюйте повний додаток за допомогою app.init() і використовуйте Supertest для HTTP запитів. Переоприділяйте охоронців/труби/інтерсептори за допомогою .overrideGuard(), .overridePipe() тощо.
Коротка відповідь
Для співбесідиPremium
Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.