使用注入对 NestJS 控制器进行单元测试

Posted

技术标签:

【中文标题】使用注入对 NestJS 控制器进行单元测试【英文标题】:Unit testing NestJS controller with injection 【发布时间】:2020-08-27 04:36:54 【问题描述】:

我需要为使用 NestJS 注入的控制器进行单元测试。

我不知道如何模拟和监视此服务 (MyEmitter)。 我需要在 beforeEach() 内部的 test.controller.spec.ts 中声明它,但是如何声明呢?

test.controller.ts

import 
  Controller,
  Body,
  Post,
 from '@nestjs/common';
import 
  WebhookDto,
 from './dto/webhook.dto';
import  MyEmitter  from './test.events';
import  InjectEventEmitter  from 'nest-emitter';

@Controller()
export class TestController 
  constructor(
    @InjectEventEmitter() private readonly myEmitter: MyEmitter,
  ) 

  @Post('webhook')
  public async postWebhook(
    @Body() webhookDto: WebhookDto,
  ): Promise<void> 
    ...
    this.myEmitter.emit('webhook', webhookDto);
  

test.controller.spec.ts

import  Test, TestingModule  from '@nestjs/testing';
import  TestController  from './test.controller';
import EventEmitter = require('events');
import  EVENT_EMITTER_TOKEN  from 'nest-emitter';
import  MyEmitter  from './test.events';

describe('Test Controller', () => 
  let testController: TestController;

  beforeEach(async () => 
    const module: TestingModule = await Test.createTestingModule(
      imports: [],
      providers: [
        
          provide: EVENT_EMITTER_TOKEN,
          useValue: 
            emit: jest.fn(),
          ,
        ,
      ],
      controllers: [TestController],
    ).compile();

    testController = module.get<TestController>(TetsController);
  );

  describe('postWebhook', () => 
    it('should send the event', async () => 
      const myEmitterSpy = jest.spyOn(myEmitter, 'emit');
      const result = await testController.postWebhook(...);
      expect(myEmitterSpy).toBeCalledTimes(1);
    );
  );
);

非常感谢您的帮助。

【问题讨论】:

【参考方案1】:

与其注入每个依赖项(应单独测试),不如使用 jest.spyOn 更好,因为控制器具有服务依赖项或可能具有其他依赖项的依赖项。

我们应该模拟将在当前测试中调用的方法。

这是示例控制器测试。

import  SampleController  from './sample.controller';
import  SampleService  from './sample.service';

describe('SampleController', () => 
  let sampleController: SampleController;
  let sampleService: SampleService;

  beforeEach(() => 
    // SampleService depends on a repository class
    // Passing null becasue SampleService will be mocked
    // So it does not need any dependencies.
    sampleService = new SampleService(null);
    // SampleController has one dependency SampleService
    sampleController = new SampleController(sampleService);
  );

  it('should be defined', async () => 
    expect(sampleController).toBeDefined();
  );

  describe('findAll', () => 
    it('should return array of samples', async () => 
      // Response of findAllByQuery Method
      // findAllByQUeryParams is a method of SampleService class.
      // I want the method to return an array containing 'test' value'.
      const expectedResult = ['test'];

      // Creating the mock method
      // The method structure is the same as the actual method structure.
      const findAllByQueryParamsMock = async (query: any) => expectedResult;

      // I am telling jest to spy on the findAllByQueryParams method
      // and run the mock method when the findAllByQueryParams method is called in the controller.
      jest
        .spyOn(sampleService, 'findAllByQueryParams')
        .mockImplementation(findAllByQueryParamsMock);

      const actualResult = await sampleController.findAll();

      expect(actualResult).toBe(expectedResult);
    );
  );
);

【讨论】:

【参考方案2】:

使用您当前的设置,最简单的方法是使用module.get(),就像您已经用于控制器一样,并传入EVENT_EMITTER_TOKEN 常量,然后将其保存到@987654324 中声明的值@block,就像let testController: TestController 的工作原理一样。这样的事情就足够了:

import  Test, TestingModule  from '@nestjs/testing';
import  TestController  from './test.controller';
import EventEmitter = require('events');
import  EVENT_EMITTER_TOKEN  from 'nest-emitter';
import  MyEmitter  from './test.events';

describe('Test Controller', () => 
  let testController: TestController;
  let myEmitter: MyEmitter;

  beforeEach(async () => 
    const module: TestingModule = await Test.createTestingModule(
      imports: [],
      providers: [
        
          provide: EVENT_EMITTER_TOKEN,
          useValue: 
            emit: jest.fn(),
          ,
        ,
      ],
      controllers: [TestController],
    ).compile();

    testController = module.get<TestController>(TetsController);
    myEmitter = module.get<MyEmitter>(EVENT_EMITTER_TOKEN);
  );

  describe('postWebhook', () => 
    it('should send the event', async () => 
      const myEmitterSpy = jest.spyOn(myEmitter, 'emit'); // you can also add on mockResponse type functions here like mockReturnValue and mockResolvedValue
      const result = await testController.postWebhook(...);
      expect(myEmitterSpy).toBeCalledTimes(1);
    );
  );
);

【讨论】:

非常感谢!!您的解决方案效果很好。终于这么简单了:) 如果您正在寻找更多示例,您可以查看I've got an entire repository 感谢您提供链接和此存储库。会有帮助的!

以上是关于使用注入对 NestJS 控制器进行单元测试的主要内容,如果未能解决你的问题,请参考以下文章

如何对 NestJS 中的控制器应用警卫进行单元测试?

nestjs 单元测试 createTestingModule 依赖注入

使用 NestJS 服务单元测试未找到连接“默认”

NestJS如何在单元测试中创建新的猫鼬模型?

Nestjs 单元测试 - 模拟方法守卫

在 NestJS 中使用 Jest 和 MongoDB 进行单元测试