Модульные тесты
Модульный (unit) тест проверяет один класс отдельно от остального приложения. Сервису статей для работы нужен репозиторий, но в тесте настоящая база не нужна: репозиторий подменяют заглушкой — объектом с теми же методами, которые возвращают заранее заданные данные. Проект NestJS уже настроен на Jest — библиотеку для запуска тестов.
Где лежат тесты
Тест лежит рядом с классом и называется так же, но с окончанием .spec.ts: для src/posts/posts.service.ts это src/posts/posts.service.spec.ts. Команда nest g service создаёт такой файл сама.
npm run testКоманда находит все файлы *.spec.ts в папке src и запускает их.
Тестовый модуль
Test.createTestingModule() собирает маленький модуль только из тех провайдеров, которые нужны тесту:
// src/posts/posts.service.spec.ts
import { NotFoundException } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Post } from './entities/post.entity';
import { PostsService } from './posts.service';
describe('PostsService', () => {
let service: PostsService;
const repository = {
findOne: jest.fn(),
find: jest.fn(),
};
beforeEach(async () => {
const moduleRef = await Test.createTestingModule({
providers: [
PostsService,
{ provide: getRepositoryToken(Post), useValue: repository },
],
}).compile();
service = moduleRef.get(PostsService);
jest.clearAllMocks();
});
it('возвращает статью по номеру', async () => {
repository.findOne.mockResolvedValue({ id: 1, title: 'Первая статья' });
const post = await service.findOne(1);
expect(post.title).toBe('Первая статья');
});
it('бросает 404, если статьи нет', async () => {
repository.findOne.mockResolvedValue(null);
await expect(service.findOne(999)).rejects.toThrow(NotFoundException);
});
});getRepositoryToken(Post) — ключ, под которым NestJS хранит репозиторий статей. Запись { provide, useValue } говорит: «вместо настоящего репозитория отдай этот объект». jest.fn() создаёт функцию-заглушку, а mockResolvedValue() задаёт промис, который она вернёт.
Результат
PASS src/posts/posts.service.spec.ts
PostsService
√ возвращает статью по номеру (5 ms)
√ бросает 404, если статьи нет (3 ms)
Tests: 2 passed, 2 totalЕсли сломать findOne() — например, убрать проверку на null, — второй тест покажет FAIL и объяснит, что ожидалось исключение.
Проверка вызовов
Заглушка запоминает, как её вызывали:
// src/posts/posts.service.spec.ts
it('запрашивает статью вместе с комментариями', async () => {
repository.findOne.mockResolvedValue({ id: 1 });
await service.findOne(1);
expect(repository.findOne).toHaveBeenCalledWith({
where: { id: 1 },
relations: { comments: true },
});
});Полезные команды
| Команда | Что делает |
|---|---|
npm run test | Запускает все модульные тесты один раз |
npm run test:watch | Перезапускает тесты при изменении файлов |
npm run test:cov | Показывает, какая часть кода покрыта тестами |
Частая ошибка
Тест падает с Nest can't resolve dependencies of the PostsService (?). Тестовый модуль не знает о TypeOrmModule — каждую зависимость сервиса нужно передать в providers самостоятельно, настоящей или заглушкой. Сгенерированный CLI тест содержит только сам сервис, поэтому после добавления репозитория его нужно дополнить.