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

Posted

技术标签:

【中文标题】Nestjs 单元测试 - 模拟方法守卫【英文标题】:Nestjs unit-test - mock method guard 【发布时间】:2019-09-14 19:47:02 【问题描述】:

我已经开始使用 NestJS 并且有一个关于嘲笑守卫的问题 用于单元测试。 我正在尝试测试一个基本的 HTTP controller,它有一个 Guard 附加方法。

当我向 Guard 注入服务时,我的问题就开始了(我需要 Guard 的 ConfigService)。

运行测试时,DI 无法解析 Guard

  ● AppController › root › should return "Hello World!"

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

我的力量失败守卫:

import  Injectable, CanActivate, ExecutionContext  from '@nestjs/common';
import  ConfigService  from './config.service';

@Injectable()
export class ForceFailGuard implements CanActivate 

  constructor(
    private configService: ConfigService,
  ) 

  canActivate(context: ExecutionContext) 
    return !this.configService.get().shouldFail;
  

规格文件:

import  CanActivate  from '@nestjs/common';
import  Test, TestingModule  from '@nestjs/testing';
import  AppController  from './app.controller';
import  AppService  from './app.service';
import  ForceFailGuard  from './force-fail.guard';

describe('AppController', () => 
  let appController: AppController;

  beforeEach(async () => 

    const mock_ForceFailGuard =  CanActivate: jest.fn(() => true) ;

    const app: TestingModule = await Test
      .createTestingModule(
        controllers: [AppController],
        providers: [
          AppService,
          ForceFailGuard,
        ],
      )
      .overrideProvider(ForceFailGuard).useValue(mock_ForceFailGuard)
      .overrideGuard(ForceFailGuard).useValue(mock_ForceFailGuard)
      .compile();

    appController = app.get<AppController>(AppController);
  );

  describe('root', () => 

    it('should return "Hello World!"', () => 
      expect(appController.getHello()).toBe('Hello World!');
    );

  );
);

我无法找到有关此问题的示例或文档。我错过了什么还是这是一个真正的问题?

感谢任何帮助, 谢谢。

【问题讨论】:

您找到解决方案了吗?我也面临同样的问题。 以后,请将相关代码粘贴到您的问题中。这使问题成为未来的证明,因为当您更改或删除您链接到的存储库时。这在这种情况下尤其需要,当这个问题是谷歌搜索“nestjs mock guard”时弹出的第一件事。 【参考方案1】:

提供的示例 repo 存在 3 个问题:

    Nestjs v6.1.1 中存在 .overrideGuard() 的错误 - 请参阅 https://github.com/nestjs/nest/issues/2070

    我已确认它已在 6.5.0 中修复。

    ForceFailGuardproviders 中,但它的依赖项(ConfigService)在创建的TestingModule 中不可用。

    如果您想模拟 ForceFailGuard,只需将其从 providers 中删除,然后让 .overrideGuard() 完成它的工作。

    mock_ForceFailGuardCanActivate 作为属性,而不是 canActivate

工作示例(nestjs v6.5.0):

import  CanActivate  from '@nestjs/common';
import  Test, TestingModule  from '@nestjs/testing';
import  AppController  from './app.controller';
import  AppService  from './app.service';
import  ForceFailGuard  from './force-fail.guard';

describe('AppController', () => 
  let appController: AppController;

  beforeEach(async () => 
    const mock_ForceFailGuard: CanActivate =  canActivate: jest.fn(() => true) ;

    const app: TestingModule = await Test
      .createTestingModule(
        controllers: [AppController],
        providers: [
          AppService,
        ],
      )
      .overrideGuard(ForceFailGuard).useValue(mock_ForceFailGuard)
      .compile();

    appController = app.get<AppController>(AppController);
  );

  describe('root', () => 
    it('should return "Hello World!"', () => 
      expect(appController.getHello()).toBe('Hello World!');
    );
  );
);

【讨论】:

【参考方案2】:

如果除了控制器单元测试之外,您还需要/想要对您的自定义保护实现进行单元测试,您可以进行类似于下面的测试,以便预期出现错误等

// InternalGuard.ts
@Injectable()
export class InternalTokenGuard implements CanActivate 
  constructor(private readonly config: ConfigService) 

  public async canActivate(context: ExecutionContext): Promise<boolean> 
    const token = this.config.get('internalToken');

    if (!token) 
      throw new Error(`No internal token was provided.`);
    

    const request = context.switchToHttp().getRequest();
    const providedToken = request.headers['authorization'];

    if (token !== providedToken) 
      throw new UnauthorizedException();
    

    return true;
  

还有你的规范文件

beforeEach(async () => 
  const module: TestingModule = await Test.createTestingModule(
    controllers: [],
    providers: [
      InternalTokenGuard,
      
        provide: ConfigService,
        useValue: 
          get: jest.fn((key: string) => 
            if (key === 'internalToken') 
              return 123;
            
            return null;
          ),
        ,
      ,
    ],
  ).compile();

  config = module.get<ConfigService>(ConfigService);
  guard = module.get<InternalTokenGuard>(InternalTokenGuard);
);

it('should throw UnauthorizedException when token is not Bearer', async () => 
  const context = 
    getClass: jest.fn(),
    getHandler: jest.fn(),
    switchToHttp: jest.fn(() => (
      getRequest: jest.fn().mockReturnValue(
        headers: 
          authorization: 'providedToken',
        ,
      ),
    )),
   as any;

  await expect(guard.canActivate(context)).rejects.toThrow(
    UnauthorizedException,
  );
  expect(context.switchToHttp).toHaveBeenCalled();
);

【讨论】:

以上是关于Nestjs 单元测试 - 模拟方法守卫的主要内容,如果未能解决你的问题,请参考以下文章

单元测试 Nest JS 过滤器捕获方法

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

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

使用请求对 NestJS 控制器进行单元测试

SpyOn TypeORM 存储库更改单元测试 NestJS 的返回值

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