【问题标题】:NestJS UseFilter not getting called when testing Service测试服务时未调用 NestJS UseFilter
【发布时间】:2020-07-16 15:32:12
【问题描述】:

我有一个 NestJS 控制器: search.controller.ts

import { Body, Controller, Post, Req, UseFilters } from '@nestjs/common';
import { HttpExceptionFilter } from '../exception/http-exception.filter';
import { SearchData } from './models/search-data.model';
import { SearchResults } from 'interfaces';
import { SearchService } from './search.service';

@Controller('')
@UseFilters(HttpExceptionFilter)
export class SearchController {
  constructor(private searchService: SearchService) {}

  @Post('api/search')
  async searchDataById(
    @Body() searchData: SearchData,
    @Req() req
  ): Promise<SearchResults> {
    return await this.searchService.getSearchResultsById(
      searchData,
      token
    );
  }
}

此搜索控制器使用名为 HttpExceptionFilter过滤器。 每当抛出 HttpException 时,都会触发此过滤器。我创建了 ServiceException,它扩展了 HttpException。每当出现错误时,我都会抛出新的 ServiceException()

HttpExceptionFilter

import {
  ArgumentsHost,
  Catch,
  ExceptionFilter,
  HttpException
} from '@nestjs/common';
import { ErrorDetails } from './error-details.interface';
import { HTTP_ERRORS } from './errors.constant';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const status = exception.getStatus();
    const api = exception.getResponse() as string;
    const errorDetails = this.getErrorDetails(api, status);

    response.status(status).json({
      status: status,
      title: errorDetails.title,
      message: errorDetails.message
    });
  }

  private getErrorDetails(api: string, status: string | number): ErrorDetails {
    const errorDetails: ErrorDetails = {
      title: HTTP_ERRORS.GENERAL.ERROR.title,
      message: HTTP_ERRORS.GENERAL.ERROR.message
    };

    // if rejection status is logged out or toke expired then redirect to login

    if (
      HTTP_ERRORS.hasOwnProperty(api) &&
      HTTP_ERRORS[api].hasOwnProperty(status)
    ) {
      errorDetails.title = HTTP_ERRORS[api][status].title;
      errorDetails.message = HTTP_ERRORS[api][status].message;
    }

    return errorDetails;
  }
}

服务异常

import { HttpException } from '@nestjs/common';

export class ServiceException extends HttpException {
  constructor(private details, private code) {
    super(details, code);
  }
}

search.service.ts

import { APIS } from '../app.constants';
import { HttpService, HttpStatus, Injectable } from '@nestjs/common';
import { SearchData, SearchResultSchema } from './models/search-data.model';
import { AppConfigService } from '../app-config/app-config.service';
import { AxiosResponse } from 'axios';
import { DataMappingPayload } from './models/data-mapping-payload.model';
import { SEARCH_SCHEMAS } from './search.constants';
import { SearchModelMapper } from './search-model-mapper.service';
import { SearchResults } from '@delfi-data-management/interfaces';
import { ServiceException } from '../exception/service.exception';

@Injectable()
export class SearchService {
  constructor(
    private searchModelMapper: SearchModelMapper,
    private configService: AppConfigService,
    private readonly httpService: HttpService
  ) {}

  // eslint-disable-next-line max-lines-per-function
  async getSearchResultsById(
    searchData: SearchData,
    stoken: string
  ): Promise<SearchResults> {
    if (searchData.filters.collectionId && searchData.viewType) {
      if (
        Object.values(SEARCH_SCHEMAS).indexOf(
          searchData.viewType as SEARCH_SCHEMAS
        ) !== -1
      ) {
        try {

         ...... some code cant paste here
          return this.searchModelMapper.generateSearchResults(
            kinds,
            mappingPayload,
            searchResultsAPI.data.results
          );
        } catch (error) {
          throw new ServiceException(
            APIS.SEARCH,
            HttpStatus.INTERNAL_SERVER_ERROR
          );
        }
      } else {
        throw new ServiceException(APIS.SEARCH, HttpStatus.BAD_REQUEST);
      }
    } else if (!searchData.filters.collectionId) {
      throw new ServiceException(APIS.SEARCH, HttpStatus.BAD_REQUEST);
    } else {
      throw new ServiceException(APIS.SEARCH, HttpStatus.BAD_REQUEST);
    }
  }

现在在单元测试

中,事情永远不会到达HttpExceptionFilter文件

search.service.spec.ts

beforeEach(async () => {
    const app = await Test.createTestingModule({
      imports: [AppConfigModule, HttpModule, SearchModule]
    }).compile();
    searchService = app.get<SearchService>(SearchService);
  });

it('should throw error message if viewType not provided', () => {
      const searchDataquery = {
        filters: {
          collectionId: 'accd'
        },
        viewType: ''
      };
      const result = searchService.getSearchResultsById(searchDataquery, 'abc');
      result.catch((error) => {
        expect(error.response).toEqual(
          generateSearchResultsErrorResponse.viewTypeError
        );
      });
    });

抛出newServiceException,内部触发HttpException不触发HttpExceptionFilter有什么原因吗?

【问题讨论】:

    标签: javascript node.js typescript nestjs httpexception


    【解决方案1】:

    过滤器在单元测试期间没有绑定,因为它们需要 Nest 正确绑定的请求上下文(这是 Nest 处理请求生命周期的方式)。由于单元测试没有传入的 HTTP 请求,因此生命周期仅被视为您明确调用的内容,在本例中为:SearchSerivce。如果您正在寻找测试过滤器,您应该设置和 e2e 类型测试,您使用 supertest 发送 HTTP 请求并允许您的过滤器在请求期间捕获。

    【讨论】:

    • 谢谢杰...这是一个可靠的答案!
    【解决方案2】:

    我需要在不同的上下文中类似的东西。在我的情况下,我需要对 graphql 的自定义过滤器进行测试。此过滤器正在捕获解析器中抛出的 HttpException。

    这是我的测试样本

    import { HttpException, HttpStatus, Logger } from '@nestjs/common'
    import {
      ApolloError,
      AuthenticationError,
      ForbiddenError,
    } from 'apollo-server-errors';
    import {ApolloExceptionFilter} from './apollo-exeption.filter'
    import { ExecutionContextHost } from '@nestjs/core/helpers/execution-context-host'
    
    describe('ApolloExceptionFilter', () => {
      const filter = new ApolloExceptionFilter(new Logger());
      const host  = new ExecutionContextHost([], null, null);
      host.setType('graphql');
    
      it('should throw apollo AuthenticationError', () => {
        const t = () => {
    
         filter.catch(new HttpException({}, HttpStatus.UNAUTHORIZED), host);
        };
        expect(t).toThrow(AuthenticationError);
    
      })
    })
    

    你可以在上面

    1. 我正在实例化过滤器
    2. 我是直接调用catch方法

    【讨论】:

      猜你喜欢
      • 2019-08-29
      • 2020-02-04
      • 2019-11-22
      • 2020-11-20
      • 2018-11-19
      • 2021-08-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多