Как правильно проводить модульное тестирование Сервиса с NestJS / Elastic - PullRequest
2 голосов
/ 27 июня 2019

Я пытаюсь протестировать сервис, использующий упругий поиск. Я хочу убедиться, что я использую правильные методы.

Я являюсь новым пользователем во многих областях этой проблемы, поэтому большинство моих попыток было связано с чтением других проблем, подобных этой, и попыткой решения тех, которые имеют смысл в моем случае использования. Я считаю, что мне не хватает поля внутри createTestingModule. Также иногда я вижу providers: [Service] и другие components: [Service].

   const module: TestingModule = await Test.createTestingModule({
      providers: [PoolJobService],
    }).compile()

Это текущая ошибка, которую я имею:

    Nest can't resolve dependencies of the PoolJobService (?). 
    Please make sure that the argument at index [0] 
    is available in the _RootTestModule context.

Вот мой код:

PoolJobService

import { Injectable } from '@nestjs/common'
import { ElasticSearchService } from '../ElasticSearch/ElasticSearchService'

@Injectable()
export class PoolJobService {
  constructor(private readonly esService: ElasticSearchService) {}

  async getPoolJobs() {
    return this.esService.getElasticSearchData('pool/job')
  }
}

PoolJobService.spec.ts

import { Test, TestingModule } from '@nestjs/testing'
import { PoolJobService } from './PoolJobService'

describe('PoolJobService', () => {
  let poolJobService: PoolJobService

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [PoolJobService],
    }).compile()

    poolJobService = module.get<PoolJobService>(PoolJobService)
  })

  it('should be defined', () => {
    expect(poolJobService).toBeDefined()
  })

Я мог бы также использовать некоторую информацию об этом, но не смог должным образом проверить это из-за текущей проблемы

  it('should return all PoolJobs', async () => {
    jest
      .spyOn(poolJobService, 'getPoolJobs')
      .mockImplementation(() => Promise.resolve([]))

    expect(await poolJobService.getPoolJobs()).resolves.toEqual([])
  })
})

1 Ответ

2 голосов
/ 27 июня 2019

Во-первых, вы правильно используете providers. Components - это специфическая вещь Angular, которой нет в Nest. Самое близкое, что у нас есть, это controllers.

То, что вы должны делать для модульного теста, - это тестирование того, что возвращает единственная функция без углубления в саму кодовую базу. В приведенном вами примере вы хотели бы смоделировать ElasticSearchServices с помощью jest.mock и подтвердить возвращение метода PoolJobService.

Nest предоставляет нам очень хороший способ сделать это с Test.createTestingModule, как вы уже указали. Ваше решение будет выглядеть следующим образом:

PoolJobService.spec.ts

import { Test, TestingModule } from '@nestjs/testing'
import { PoolJobService } from './PoolJobService'
import { ElasticSearchService } from '../ElasticSearch/ElasticSearchService'

describe('PoolJobService', () => {
  let poolJobService: PoolJobService
  let elasticService: ElasticSearchService // this line is optional, but I find it useful when overriding mocking functionality

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        PoolJobService,
        {
          provide: ElasticSearchService,
          useValue: {
            getElasticSearchData: jest.fn()
          }
        }
      ],
    }).compile()

    poolJobService = module.get<PoolJobService>(PoolJobService)
    elasticService = module.get<ElasticSearchService>(ElasticSearchService)
  })

  it('should be defined', () => {
    expect(poolJobService).toBeDefined()
  })
  it('should give the expected return', async () => {
    elasticService.getElasticSearchData = jest.fn().mockReturnValue({data: 'your object here'})
    const poolJobs = await poolJobService.getPoolJobs()
    expect(poolJobs).toEqual({data: 'your object here'})
  })

Вы можете достичь той же функциональности с jest.spy вместо mock, но от вас зависит, как вы захотите реализовать эту функциональность.

Как основное правило, что бы ни было в вашем конструкторе, вам нужно будет его смоделировать, и пока вы его высмеиваете, все, что находится в конструкторе смоделированного объекта, можно игнорировать. Счастливого тестирования!

РЕДАКТИРОВАТЬ 6/27/2019

О том, почему мы высмеиваем ElasticSearchService: модульное тестирование предназначено для тестирования определенного сегмента кода, а не для взаимодействия с кодом вне проверяемой функции. В этом случае мы тестируем функцию getPoolJobs класса PoolJobService. Это означает, что нам на самом деле не нужно изо всех сил и подключаться к базе данных или внешнему серверу, так как это может сделать наши тесты медленными / подверженными сбоям, если сервер не работает / изменяет данные, которые мы не хотим изменять. Вместо этого мы смоделируем внешние зависимости (ElasticSearchService), чтобы вернуть значение, которым мы можем управлять ( в теории это будет выглядеть очень похоже на реальные данные, но для контекста этого вопроса я сделал его строкой * 1033) *). Затем мы проверяем, что getPoolJobs возвращает значение, которое возвращает функция ElasticSearchService getElasticSearchData, так как это функциональность этой функции.

Это кажется довольно тривиальным в этом случае и может показаться бесполезным, но когда после внешнего вызова начинает действовать бизнес-логика, становится понятно, почему мы хотим издеваться. Скажем, у нас есть какое-то преобразование данных, чтобы сделать строку заглавной, прежде чем мы вернемся из getPoolJobs метода

export class PoolJobService {

  constructor(private readonly elasticSearchService: ElasticSearchService) {}

  getPoolJobs(data: any): string {
    const returnData = this.elasticSearchService.getElasticSearchData(data);
    return returnData.toUpperCase();
  }
}

Отсюда в тесте мы можем сказать getElasticSearchData, что возвращать, и легко утверждать, что getPoolJobs выполняет необходимую логику (утверждая, что строка действительно в верхнем регистре), не беспокоясь о логике внутри getElasticSearchData или о создании каких-либо сетевые звонки. Для функции, которая ничего не делает, но возвращает выходные данные других функций, она чувствует немного как обман ваших тестов, но на самом деле это не так. Вы следуете шаблонам тестирования, используемым большинством других в сообществе.

Когда вы перейдете к тестам integration и e2e, вам понадобятся внешние выноски и убедитесь, что ваш поисковый запрос возвращает то, что вы ожидаете, но это выходит за рамки модульного тестирования.

...