Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Модульные тесты

Модульный (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 тест содержит только сам сервис, поэтому после добавления репозитория его нужно дополнить.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.