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

Posted

技术标签:

【中文标题】如何对 NestJS 中的控制器应用警卫进行单元测试?【英文标题】:How can I unit test that a guard is applied on a controller in NestJS? 【发布时间】:2020-05-03 03:53:51 【问题描述】:

我在 NestJS 中配置了一个控制器,我想检查是否设置了适当的保护 - 有没有人有一个如何做到的例子?

这个(删节的)示例作为应用程序可以正常工作,所以我只是在测试指导之后。

您会在用户测试中注意到我调用Reflect.getMetadata 的测试。我正在追求这样的东西 - 当我在 __guards__ 元数据上检查它时,这是一个函数,我正在努力模拟它,所以我可以检查它是否在设置时与 AuthGuard('jwt') 一起应用。

用户.controller.ts

@Controller('/api/user')
export class UserController 
  @UseGuards(AuthGuard('jwt'))
  @Get()
  user(@Request() req) 
    return req.user;
  

User.controller.spec.ts

describe('User Controller', () => 
  // beforeEach setup as per the cli generator

  describe('#user', () => 
    beforeEach(() => 
      // This is how I'm checking the @Get() decorator is applied correctly - I'm after something for __guards__
      expect(Reflect.getMetadata('path', controller.user)).toBe('/');
      expect(Reflect.getMetadata('method', controller.user)).toBe(RequestMethod.GET);
    );

    it('should return the user', () => 
      const req = 
        user: 'userObj',
      ;

      expect(controller.user(req)).toBe(req.user);
    );
  );
);

【问题讨论】:

我曾经使用 e2e 进行此测试,但在我看来,它们绝对是完全有效的回归测试。 @RuslanGonzalez 是的,e2e 测试很重要。我认为 e2e 测试检查它们是否应用了正确的功能,而单元测试检查它们是否被应用——两者对于它们协同工作都很重要且至关重要。不过,单元测试往往更快 【参考方案1】:

对于它的价值,您不需要测试框架提供的装饰器是否也设置了您所期望的。这就是为什么框架开始对它们进行测试的原因。不过,如果您想检查装饰器是否确实设置了预期的元数据you can see that done here。

如果您只是想测试守卫,您可以直接实例化 GuardClass 并通过提供 ExecutionContext 对象来测试其 canActivate 方法。 I've got an example here。该示例使用library that creates mock objects for you(从那时起为renamed),但它的想法是您将创建一个类似

的对象
const mockExecutionContext: Partial<
  Record<
    jest.FunctionPropertyNames<ExecutionContext>,
    jest.MockedFunction<any>
  >
> = 
  switchToHttp: jest.fn().mockReturnValue(
    getRequest: jest.fn(),
    getResponse: jest.fn(),
  ),
;

其中getRequestgetResponse 返回HTTP 请求和响应对象(或至少它们的一部分)。要仅使用此对象,您还需要使用 as any 以防止 Typescript 抱怨太多。

【讨论】:

这不是我真正想做的事。我希望确保为该方法设置了保护装饰器。我不在乎装饰器在下面做了什么(出于你提到的原因),我已经围绕警卫本身进行了测试。如果这是一个“经典”的 Express 应用程序,我可以测试中间件是否应用于路由,这是我在这里尝试实现的,但使用装饰器 我不太确定我是否理解您当时想要实现的目标。第一个链接显示了显示元数据在类和类方法上正确设置的测试,这就是“设置”守卫的方式。如果你想测试当你调用路由时守卫被执行,那么你需要设置 supertest 来调用路由。也许我不明白你要做什么 是的,e2e 测试是实现这一目标的一种选择。这很可能是一种更合适的方式。我给出的示例确实检查了是否设置了正确的元数据,但那是因为我无法找到更好的方法来测试应用了 @Get 装饰器(我愿意接受建议)。我想要实现的是一个测试,以确保设置了适当的保护措施——我更喜欢通过单元测试而不是 e2e 来完成,因为它会出现在覆盖率报告中,但这不会破坏交易跨度> 看来元数据的反射仍然是你最好的选择。在上面的示例中,您可以进行类似expect(Reflect.getMetadata('__guards__', UserController.user)).toEqual(MixinAuthGuard) 的测试。 MixinAuthGuard 是 mixin AuthGuard('jwt') 产生(或应该产生)的类。这将断言应用于 UserController.user 方法的守卫(即 GET /api/user 路由)将是正确的守卫 另请参阅相关问题 (***.com/questions/62595603/…),那里有一个用于模拟 ExecutionContext 的最新示例。还值得一提——包@golevelup/nestjs-testing已重命名为@golevelup/ts-jest,见github.com/golevelup/nestjs/issues/265【参考方案2】:

我意识到它并不完全是您正在寻找的答案,但是在@Jay McDoniel 的答案的基础上,我使用以下内容来测试自定义装饰器在控制器功能上的存在(尽管我不能 100% 确定这是否正确测试非自定义守卫的方法)

import  Controller  from '@nestjs/common';
import  UseGuards  from '@nestjs/common';
import  JwtAuthGuard  from './jwtAuthGuard';

@Controller()
export class MyController 

  @UseGuards(JwtAuthGuard)
  user() 
    ...
  

it('should ensure the JwtAuthGuard is applied to the user method', async () => 
  const guards = Reflect.getMetadata('__guards__', MyController.prototype.user)
  const guard = new (guards[0])

  expect(guard).toBeInstanceOf(JwtAuthGuard)
);

对于控制器

it('should ensure the JwtAuthGuard is applied to the controller', async () => 
  const guards = Reflect.getMetadata('__guards__', MyController)
  const guard = new (guards[0])

  expect(guard).toBeInstanceOf(JwtAuthGuard)
);

【讨论】:

太好了,谢谢。正如我在 OP 中所说,我不是在测试装饰器的功能之后,只是测试了它们是否应用了适当的配置。由于我们在代码中依赖它,我认为这应该是单元测试的一部分

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

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

GraphQL + NestJS - 我如何在警卫中访问@Args?

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

e2e 如何与 guard nestjs

在 NestJS 中,有没有办法将数据从 Guards 传递到控制器?

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