Nest JS UseFilter не вызывается при тестировании Сервиса - PullRequest
0 голосов
/ 04 апреля 2020

У меня есть гнездо JS Контроллер: 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;
  }
}

ServiceException

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.spe c .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
        );
      });
    });

Есть ли причина, по которой генерировать новое ServiceException, которое внутренне вызывает HttpException, не вызывает HttpExceptionFilter?

1 Ответ

1 голос
/ 04 апреля 2020

Фильтры не связаны во время модульных тестов, потому что они требуют, чтобы контекст запроса для Nest связывался правильно (именно так Nest обрабатывает жизненный цикл запроса). Поскольку в модульных тестах нет входящего HTTP-запроса, жизненный цикл рассматривается только как явный вызов, в данном случае: SearchSerivce. Если вы хотите протестировать фильтр, вам нужно настроить и e2e type test, где вы используете supertest для отправки HTTP-запросов и позволяете фильтру перехватывать во время запроса.

...