Nodejs/NestJS ExceptionFilter Catch 方法的 Jest 单元测试

Posted

技术标签:

【中文标题】Nodejs/NestJS ExceptionFilter Catch 方法的 Jest 单元测试【英文标题】:Jest Unit Test for Nodejs/NestJS ExceptionFilter Catch Method 【发布时间】:2020-03-30 23:22:46 【问题描述】:

这是我用 Typescript 为 Nodejs/Nestjs 编写的 BadRequestExceptionFilter


@Catch(BadRequestException)
export class BadRequestExceptionFilter implements ExceptionFilter 
  constructor(private logger: AppLoggerService) 
  catch(exception: BadRequestException, host: ArgumentsHost) 
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

    const status =
      exception instanceof BadRequestException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    const message = 
      Title: exception.message.error,
      Type: 'Exception - BadRequestExceptionFilter',
      Detail: exception.message,
      Status: '',
    ;

    this.logger.error(message, '');
    response.code(status).send(
      statusCode: status,
      ...(exception.getResponse() as object),
      timestamp: 'Exception - BadRequestException' + new Date().toISOString(),
    );
  


这是我的单元测试和 2 断言在这里完成。 第一个 assert 是检查是否调用了 mockLogger.error。这是工作。 第二个断言检查是否调用了 response.code(status).send()。 但得到这个错误。 期望(jest.fn()).toBeCalled()

Expected number of calls: >= 1
Received number of calls:    0

const mockLogger =  error: jest.fn() ;

const mockContext: any = 
  switchToHttp: () => (
    getRequest: () => (
      url: 'mock-url',
    ),
    getResponse: () => 
      const response = 
        code: jest.fn().mockReturnThis(),
        send: jest.fn().mockReturnThis(),
      ;
      return response;
    ,
  ),
;

describe('BadRequestExceptionFilter', () => 
  let filter: BadRequestExceptionFilter;

  beforeEach(() => 
    filter = new BadRequestExceptionFilter(mockLogger as any);
  );

  it('should catch and log the error', () => 
    const mockException: BadRequestException = new BadRequestException();

    mockException.name = 'BadRequestException';
    mockException.getResponse = jest.fn().mockReturnValue(of('getResponse'));
    mockException.getStatus = () => 404;

    jest.fn(mockContext.switchToHttp().getResponse().send);
    filter.catch(mockException, mockContext);
    expect(mockLogger.error).toBeCalled();

    expect(
      mockContext
        .switchToHttp()
        .getResponse()
        .code().send,
    ).toBeCalled();
  );

);

【问题讨论】:

您找到解决方案了吗?我对此有点卡住了。 @tsadkanyitbarek 检查下面我的答案,我已经成功编写测试用例了。 【参考方案1】:

在您的过滤器中,您有...(expection.getResponse() as object),但exception 上没有函数getResponse()。相反,您需要先切换到 http 上下文

【讨论】:

【参考方案2】:

我做了这样的事情,它奏效了!

export const contextMock = (roles?: string[]) => 
    const ctx: any = 
    ctx.switchToHttp = jest.fn().mockReturnValue(
        getRequest: jest.fn().mockReturnValue(requestMock()),
        getResponse: jest.fn().mockReturnValue(responseMock()),
    )
    ctx.getHandler = jest.fn().mockReturnValue( roles ) as Function

    return ctx


-----------------------------------------------

const ctxMock = contextMock() as any

expect(ctxMock.switchToHttp).toHaveBeenCalled()
expect(ctxMock.switchToHttp().getResponse).toHaveBeenCalled()
expect(ctxMock.switchToHttp().getResponse().status).toHaveBeenCalled()
expect(ctxMock.switchToHttp().getResponse().json).toHaveBeenCalled()

据我了解,我们总是必须为“期望”传递一个模拟。

【讨论】:

【参考方案3】:

我认为使用 Nest 测试模块编写异常过滤器的测试用例很简单。检查下面我是如何实现相同的:

// all-exception.filter.spec.ts
import 
    Test,
    TestingModule
 from '@nestjs/testing';
import 
    HttpStatus,
    HttpException
 from '@nestjs/common';

import  AllExceptionsFilter  from './all-exceptions.filter';
import  AppLoggerService  from '../services/logger/app-logger.service';

const mockAppLoggerService = 
    info: jest.fn(),
    error: jest.fn(),
    warn: jest.fn(),
    debug: jest.fn()
;
const mockJson = jest.fn();
const mockStatus = jest.fn().mockImplementation(() => (
    json: mockJson
));
const mockGetResponse = jest.fn().mockImplementation(() => (
    status: mockStatus
));
const mockHttpArgumentsHost = jest.fn().mockImplementation(() => (
    getResponse: mockGetResponse,
    getRequest: jest.fn()
));

const mockArgumentsHost = 
    switchToHttp: mockHttpArgumentsHost,
    getArgByIndex: jest.fn(),
    getArgs: jest.fn(),
    getType: jest.fn(),
    switchToRpc: jest.fn(),
    switchToWs: jest.fn()
;

describe('System header validation service', () => 
    let service: AllExceptionsFilter;

    beforeEach(async () => 
        jest.clearAllMocks();
        const module: TestingModule = await Test.createTestingModule(
            providers: [
                AllExceptionsFilter,
                
                    provide: AppLoggerService,
                    useValue: mockAppLoggerService
                ,
            ]
        ).compile();
        service = module.get<AllExceptionsFilter>(AllExceptionsFilter);
    );

    describe('All exception filter tests', () => 

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

        it('Http exception', () => 
            service.catch(
                new HttpException('Http exception', HttpStatus.BAD_REQUEST),
                mockArgumentsHost
            );
            expect(mockHttpArgumentsHost).toBeCalledTimes(1);
            expect(mockHttpArgumentsHost).toBeCalledWith();
            expect(mockGetResponse).toBeCalledTimes(1);
            expect(mockGetResponse).toBeCalledWith();
            expect(mockStatus).toBeCalledTimes(1);
            expect(mockStatus).toBeCalledWith(HttpStatus.BAD_REQUEST);
            expect(mockJson).toBeCalledTimes(1);
            expect(mockJson).toBeCalledWith(
                message: 'Http exception'
            );
        );
    );
);

【讨论】:

当我尝试运行此测试时,我得到一个 ctx.getRequest() is not a function。知道为什么吗? @iAviator 我已经编辑了上面的代码模拟 getRequest fn 添加。您可以将 jest.fn() 替换为您的自定义模拟

以上是关于Nodejs/NestJS ExceptionFilter Catch 方法的 Jest 单元测试的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法将默认值设置为缺少/可选的 JSON 属性?