使用 NestJS/Elastic 对服务进行单元测试的正确方法是什么

Cha*_*oon 11 elasticsearch graphql nestjs typegraphql

我正在尝试对使用弹性搜索的服务进行单元测试。我想确保我使用了正确的技术。

我是这个问题许多领域的新用户,所以我的大部分尝试都是通过阅读与此类似的其他问题并尝试在我的用例中有意义的问题。我相信我缺少 createTestingModule 中的一个字段。也有时我看到providers: [Service]和其他人components: [Service]

   const module: TestingModule = await Test.createTestingModule({
      providers: [PoolJobService],
    }).compile()
Run Code Online (Sandbox Code Playgroud)

这是我当前的错误:

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

这是我的代码:

池作业服务

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

PoolJobService.spec.ts

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')
  }
}
Run Code Online (Sandbox Code Playgroud)

我也可以对此使用一些见解,但由于当前的问题,无法正确测试

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()
  })

Run Code Online (Sandbox Code Playgroud)

Jay*_*iel 20

首先,您使用providers. ComponentsAngularNest 中不存在的特定事物。我们拥有的最接近的东西是controllers.

您应该为单元测试做的是测试单个函数的返回值,而无需深入研究代码库本身。在您提供的示例中,您希望ElasticSearchServices用 a模拟您jest.mockPoolJobService方法并断言方法的返回。

Test.createTestingModule正如您已经指出的那样,Nest 为我们提供了一种非常好的方式来做到这一点。您的解决方案将类似于以下内容:

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'})
  })

Run Code Online (Sandbox Code Playgroud)

您可以使用 ajest.spy而不是a来实现相同的功能mock,但这取决于您希望如何实现该功能。

作为一个基本规则,无论你的构造函数中有什么,你都需要模拟它,只要你模拟它,被模拟对象的构造函数中的任何东西都可以被忽略。测试愉快!

编辑6/27/2019

关于我们为什么要模拟ElasticSearchService:单元测试旨在测试特定的代码段,而不是与测试函数之外的代码进行交互。在这种情况下,我们正在测试的功能getPoolJobs的的PoolJobService类。这意味着我们真的不需要全力以赴连接到数据库或外部服务器,因为如果服务器关闭/修改我们不想修改的数据,这可能会使我们的测试变慢/容易中断。相反,我们模拟了外部依赖项 ( ElasticSearchService) 以返回一个我们可以控制的值(理论上这看起来与真实数据非常相似,但对于这个问题的上下文,我将其设为 string)。然后我们测试getPoolJobs返回ElasticSearchServicegetElasticSearchData函数返回的值,因为这是该函数的功能。

在这种情况下,这似乎相当微不足道,可能看起来没有用,但是当外部调用之后开始有业务逻辑时,我们就很清楚为什么要模拟。假设我们在从getPoolJobs方法返回之前进行了某种数据转换以使字符串大写

export class PoolJobService {

  constructor(private readonly elasticSearchService: ElasticSearchService) {}

  getPoolJobs(data: any): string {
    const returnData = this.elasticSearchService.getElasticSearchData(data);
    return returnData.toUpperCase();
  }
}
Run Code Online (Sandbox Code Playgroud)

从这里在测试中,我们可以知道getElasticSearchData要返回什么并轻松断言它getPoolJobs是否是必要的逻辑(断言字符串确实是 upperCased),而无需担心内部逻辑getElasticSearchData或进行任何网络调用。对于一个只返回另一个函数输出的函数,感觉有点像在测试中作弊,但实际上你不是。您正在遵循社区中大多数其他人使用的测试模式。

当您在移动integratione2e测试,那么你会希望你的外部标注,并确保您的搜索查询将返回你所期望的,但毕竟是单元测试的范围之内。