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

时间:2019-06-26 22:38:13

标签: elasticsearch graphql nestjs type-graphql

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

我是该问题很多领域的新用户,因此我的大部分尝试都是通过阅读与之类似的其他问题并尝试在我的用例中有意义的问题。我相信我在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 个答案:

答案 0 :(得分:2)

首先,您对使用providers是正确的。 ComponentsAngular的特定事物,在Nest中不存在。我们最接近的是controllers

对于单元测试,您应该做的是测试单个函数的返回结果,而不必更深入地研究代码库本身。在您提供的示例中,您想用ElasticSearchServices模拟jest.mock并断言PoolJobService方法的返回。

如您已经指出的,嵌套为我们提供了一种非常不错的方式来使用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来实现相同的功能,但这取决于您要如何实现该功能。

作为基本规则,无论构造函数中的内容是什么,都需要对其进行模拟,并且只要对其进行模拟,就可以忽略模拟对象的构造函数中的任何内容。测试愉快!

编辑 2019年6月27日

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

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

getPoolJobs

在测试中,我们可以从这里告诉export class PoolJobService { constructor(private readonly elasticSearchService: ElasticSearchService) {} getPoolJobs(data: any): string { const returnData = this.elasticSearchService.getElasticSearchData(data); return returnData.toUpperCase(); } } 返回什么,并轻松断言getElasticSearchData是否确实是必要的逻辑(断言字符串确实是上框的),而不必担心{{1 }}或进行任何网络通话。对于只执行其他功能但返回其他功能输出的函数,它确实有点感觉,就像在测试中作弊一样,但实际上并非如此。您正在遵循社区中其他大多数人使用的测试模式。

当继续进行getPoolJobsgetElasticSearchData测试时,您将需要使用外部标注,并确保您的搜索查询返回的是您期望的结果,但这不在范围内单元测试。