13

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

我是这个问题的许多领域的新用户,所以我的大部分尝试都是通过阅读与此类似的其他问题并尝试在我的用例中有意义的问题。我相信我缺少 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([])
  })
})

4

1 回答 1

23

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

您应该为单元测试做的是测试单个函数的返回值,而无需深入挖掘代码库本身。在您提供的示例中,您可能想ElasticSearchServices用 a模拟您的方法jest.mock并断言该PoolJobService方法的返回。

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

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

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

编辑2019 年 6 月 27 日

关于我们模拟的原因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时,您将需要外部标注并确保您的搜索查询返回您期望的内容,但这超出了单元测试的范围。

于 2019-06-27T15:47:19.707 回答