问题描述
我正在尝试对使用弹性搜索的服务进行单元测试。我想确保使用正确的技术。
Im trying to unit test a Service that uses elastic search. I want to make sure I am using the right techniques.
我是该问题很多领域的新用户,所以我的大部分尝试都是通过阅读其他类似问题为此,并尝试在我的用例中有意义的方法。我相信我在createTestingModule中缺少一个字段。有时我还会看到提供者:[服务]
和其他组件:[服务]
。
I am new user to many areas of this problem, so most of my attempts have been from reading other problems similar to this and trying out the ones that make sense in my use case. I believe I am missing a field within the createTestingModule. Also sometimes I see providers: [Service]
and others 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
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()
})
我也可以对此使用一些见识,但由于当前问题,我无法对其进行正确的测试
I could also use some insight on this, but haven't been able to properly test this because of the current issue
it('should return all PoolJobs', async () => {
jest
.spyOn(poolJobService, 'getPoolJobs')
.mockImplementation(() => Promise.resolve([]))
expect(await poolJobService.getPoolJobs()).resolves.toEqual([])
})
})
推荐答案
首先,您正确使用提供者
。 Components
是特定于 Angular
的东西,在Nest中不存在。我们最接近的是 controllers
。
First off, you're correct about using providers
. Components
is an Angular
specific thing that does not exist in Nest. The closest thing we have are controllers
.
单元测试应该测试的是单个函数的返回值无需深入研究代码库本身。在您提供的示例中,您想使用 jest.mock
模拟您的 ElasticSearchServices
并断言返回 PoolJobService
方法。
What you should be doing for a unit test is testing what the return of a single function is without digging deeper into the code base itself. In the example you've provided you would want to mock out your ElasticSearchServices
with a jest.mock
and assert the return of the PoolJobService
method.
嵌套为我们提供了一种非常不错的方法,使我们可以使用 Test.createTestingModule
正如您已经指出的那样。您的解决方案将类似于以下内容:
Nest provides a very nice way for us to do this with Test.createTestingModule
as you've already pointed out. Your solution would look similar to the following:
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'})
})
您可以使用玩笑来实现相同的功能.spy
而不是 mock
,但这取决于您如何实现功能。
You could achieve the same functionality with a jest.spy
instead of a mock
, but that is up to you on how you want to implement the functionality.
作为基本规则,无论构造函数中的内容是什么,都需要对其进行模拟,并且只要对其进行模拟,就可以忽略模拟对象的构造函数中的任何内容。测试愉快!
As a basic rule, whatever is in your constructor, you will need to mock it, and as long as you mock it, whatever is in the mocked object's constructor can be ignored. Happy testing!
编辑 2019年6月27日
EDIT 6/27/2019
关于我们为何嘲笑 ElasticSearchService
:单元测试旨在测试特定的代码段,而不与被测函数外部的代码进行交互。在这种情况下,我们正在测试 PoolJobService
类的函数 getPoolJobs
。这意味着我们实际上并不需要全力以赴连接到数据库或外部服务器,因为如果服务器关闭/修改了我们不想修改的数据,这会使我们的测试变慢/容易中断。取而代之的是,我们模拟外部依赖项( ElasticSearchService
)以返回一个我们可以控制的值(理论上,这看起来与真实数据非常相似,但是对于这个问题的上下文中,我将其设为字符串)。然后我们测试 getPoolJobs
返回的值是 ElasticSearchService
的 getElasticSearchData
函数将返回,因为它就是该函数的功能。
About why we mock ElasticSearchService
: A unit test is designed to test a specific segment of code and not make interactions with code outside of the tested function. In this case, we are testing the function getPoolJobs
of the PoolJobService
class. This means that we don't really need to go all out and connect to a database or external server as this could make our tests slow/prone to breaking if the server is down/modify data we don't want to modify. Instead, we mock out the external dependencies (ElasticSearchService
) to return a value that we can control (in theory this will look very similar to real data, but for the context of this question I made it a string). Then we test that getPoolJobs
returns the value that ElasticSearchService
's getElasticSearchData
function returns, as that is the functionality of this function.
在这种情况下,这似乎微不足道,而且似乎没有用,但是在外部调用之后开始出现业务逻辑时然后很清楚为什么我们要嘲笑。假设在从 getPoolJobs
方法返回之前,我们已经进行了某种数据转换以使字符串变为大写
This seems rather trivial in this case and may not seem useful, but when there starts to be business logic after the external call then it becomes clear why we would want to mock. Say that we have some sort of data transformation to make the string uppercase before we return from the getPoolJobs
method
export class PoolJobService {
constructor(private readonly elasticSearchService: ElasticSearchService) {}
getPoolJobs(data: any): string {
const returnData = this.elasticSearchService.getElasticSearchData(data);
return returnData.toUpperCase();
}
}
从这里的测试中我们可以知道 getElasticSearchData
返回的内容,并轻松断言 getPoolJobs
确实是必要的逻辑(断言字符串确实是上壳的),而不必担心<$ c内的逻辑$ c> getElasticSearchData 或进行任何网络调用。对于只返回其他函数输出却什么也不做的函数,它确实有点感觉,就像在测试中作弊一样,但实际上并非如此。您将遵循社区中其他大多数人使用的测试模式。
From here in the test we can tell getElasticSearchData
what to return and easily assert that getPoolJobs
does it's necessary logic (asserting that the string really is upperCased) without worrying about the logic inside getElasticSearchData
or about making any network calls. For a function that does nothing but return another functions output, it does feel a little bit like cheating on your tests, but in reality you aren't. You're following the testing patterns used by most others in the community.
继续进行集成
和 e2e
测试,那么您将需要使用外部标注,并确保搜索查询返回的是您期望的结果,但这超出了单元测试的范围。
When you move on to integration
and e2e
tests, then you'll want to have your external callouts and make sure that your search query is returning what you expect, but that is outside the scope of unit testing.
这篇关于使用NestJS / Elastic进行单元测试服务的正确方法是什么的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!