简体   繁体   中英

What is the proper way to unit test Service with NestJS/Elastic

Im trying to unit test a Service that uses elastic search. I want to make sure I am using the right techniques.

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

This is the current error I have:

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

Here is my code:

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

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

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 .

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.

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:

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

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!

EDIT 6/27/2019

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.

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

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM