Я пытаюсь провести модульное тестирование службы, использующей эластичный поиск. Я хочу убедиться, что использую правильные методы.
Я новичок во многих областях этой проблемы, поэтому большинство моих попыток было связано с чтением других проблем, подобных этой, и опробованием тех, которые имеют смысл в моем случае использования. Я считаю, что мне не хватает поля в 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.
Вот мой код:
ПулДжобСервис
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([])
})
})
Во-первых, вы правы насчет использования providers. Components — это Angular конкретная вещь, которой нет в Nest. Самое близкое, что у нас есть, это controllers.
Что вы должны сделать для модульного теста, так это проверить, что возвращает одна функция, не углубляясь в саму кодовую базу. В приведенном вами примере вы хотели бы смоделировать свой ElasticSearchServices с помощью jest.mock и утвердить возврат метода PoolJobService.
Nest предоставляет нам очень хороший способ сделать это с помощью Test.createTestingModule, как вы уже указали. Ваше решение будет выглядеть примерно так:
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, но это зависит от вас, как вы хотите реализовать эту функциональность.
Как правило, все, что находится в вашем конструкторе, вам нужно будет имитировать, и пока вы его имитируете, все, что находится в конструкторе имитируемого объекта, может быть проигнорировано. Удачного тестирования!
РЕДАКТИРОВАТЬ 27.06.2019
О том, почему мы издеваемся ElasticSearchService: модульный тест предназначен для проверки определенного сегмента кода и предотвращения взаимодействия с кодом за пределами тестируемой функции. В данном случае мы тестируем функцию getPoolJobs класса PoolJobService. Это означает, что нам на самом деле не нужно из кожи вон лезть и подключаться к базе данных или внешнему серверу, так как это может сделать наши тесты медленными/склонными к поломке, если сервер не работает/изменить данные, которые мы не хотим изменять. Вместо этого мы моделируем внешние зависимости (ElasticSearchService), чтобы вернуть значение, которым мы можем управлять (теоретически это будет очень похоже на реальные данные, но для контекста этого вопроса я сделал это строкой). Затем мы проверяем, что getPoolJobs возвращает значение, которое возвращает функция ElasticSearchServicegetElasticSearchData, поскольку это функциональность этой функции.
В данном случае это кажется довольно тривиальным и может показаться бесполезным, но когда после внешнего вызова начинается бизнес-логика, становится ясно, зачем нам моки. Скажем, у нас есть какое-то преобразование данных, чтобы сделать строку прописной, прежде чем мы вернемся из метода 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, вам понадобятся внешние выноски и убедитесь, что ваш поисковый запрос возвращает то, что вы ожидаете, но это выходит за рамки модульного тестирования.
Правильно, мы не заботимся о зависимостях ElasticSearchService, потому что сама служба издевается. Если вместо этого вы выберете предложенный providers: [PoolJobService, ElasticSearchService, APIService], вам нужно будет предоставить все зависимости ElasticSearchService и APIService, поскольку в противном случае Nest просто создаст экземпляр класса по умолчанию (то есть то, что выполняется, когда вы запускаете свой сервер), и вам потребуется доступ ко всем зависимостям для правильного создания экземпляра эти классы. createTestingModule ничего не имитирует для вас, но позволяет использовать макеты вместо полных классов.
Я понимаю! В этом есть смысл. У меня есть еще один вопрос, выходящий за рамки этого вопроса, но касающийся теста 'should give the expected return'. И ваш, и мой пытаются выполнить один и тот же тест, но я не могу не чувствовать, что это бесполезный тест. Мне кажется, что мы издеваемся над функцией, а затем ожидаем, что эта издевательская функция вернет значение, которое МЫ установили для нее. Опять же, это отдельный вопрос и недостаток знаний по тестированию с моей стороны, но не могли бы вы объяснить, почему это полезный/бесполезный тест?
Конечно, я отредактирую свой ответ, чтобы он более подробно объяснял, почему я издеваюсь над тем, как показал.
Большое спасибо за все подробности. Вы очень помогли!! @Джей Макдониэль
Потрясающий! Это кажется правильным решением. Хотя мне нужно пояснение. Мой ElasticSearchService также имеет внедренную службу в своем конструкторе, НО нас это не волнует, потому что это решение полностью издевается над ElasticSearchService. Это правильно? Также я придумал такое решение, как этот ```константный модуль: TestingModule = await Test.createTestingModule({ provider: [PoolJobService, ElasticSearchService, APIService], }).compile() ```, прежде чем читать это решение. Теория заключалась в том, что createTestingModule делал все издевательства за нас. это неправильно?