首页 > 解决方案 > 测试服务时未调用 NestJS UseFilter

问题描述

我有一个 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时,都会触发此过滤器。我创建了扩展HttpException的ServiceException。每当出现错误时,我都会抛出 new 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);
  }
}

搜索服务.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
        );
      });
    });

抛出new ServiceException,内部触发HttpException不触发HttpExceptionFilter是有原因的吗?

标签: javascriptnode.jstypescriptnestjshttpexception

解决方案


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


推荐阅读