Тестирование модуля перехвата запросов и ответов Nest Js - PullRequest
1 голос
/ 07 января 2020

Я хотел бы регистрировать входящие запросы и исходящие ответы для моего API. Я создал перехватчик запросов и перехватчик ответов, как описано здесь

https://docs.nestjs.com/interceptors

Таким образом, перехватчик запросов регистрирует только объект запроса

@Injectable()
export class RequestInterceptor implements NestInterceptor {
  private readonly logger: Logger = new Logger(RequestInterceptor.name, true);

  public intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const { originalUrl, method, params, query, body } = context.switchToHttp().getRequest();

    this.logger.debug({ originalUrl, method, params, query, body }, this.intercept.name);

    return next.handle();
  }
}

и перехватчик ответа ожидает исходящего ответа и регистрирует код состояния и объект ответа позже

@Injectable()
export class ResponseInterceptor implements NestInterceptor {
  private readonly logger: Logger = new Logger(ResponseInterceptor.name, true);

  public intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const { statusCode } = context.switchToHttp().getResponse();

    return next.handle().pipe(
      tap((responseData: any) =>
        this.logger.debug({ statusCode, responseData }, this.intercept.name),
      ),
    );
  }
}

Я хотел бы проверить их, но, к сожалению, почти не имею опыта в тестировании. Я попытался начать с перехватчика запроса и придумал этот

const executionContext: any = {
  switchToHttp: jest.fn().mockReturnThis(),
  getRequest: jest.fn().mockReturnThis(),
};

const nextCallHander: CallHandler<any> = {
  handle: jest.fn(),
};

describe('RequestInterceptor', () => {
  let interceptor: RequestInterceptor;

  beforeEach(() => {
    interceptor = new RequestInterceptor();
  });

  describe('intercept', () => {
    it('should fetch the request object', (done: any) => {
      const requestInterception: Observable<any> = interceptor.intercept(executionContext, nextCallHander);

      requestInterception.subscribe({
        next: value => {
          // ... ??? ...
        },
        error: error => {
          throw error;
        },
        complete: () => {
          done();
        },
      });
    });
  });
});

. В настоящее время я не знаю, что передать в следующий обратный вызов, но когда я пытаюсь запустить тест, то он говорит, что Переменная requestInterception не определена. Таким образом, тест не пройден до достижения следующего обратного вызова. Таким образом, я получаю сообщение об ошибке:

TypeError: Невозможно прочитать свойство 'подписка' из неопределенного

Я также попытался проверить перехватчик ответа и придумал это

const executionContext: any = {
  switchToHttp: jest.fn().mockReturnThis(),
  getResponse: jest.fn().mockReturnThis()
};

const nextCallHander: CallHandler<any> = {
  handle: jest.fn()
};

describe("ResponseInterceptor", () => {
  let interceptor: ResponseInterceptor;

  beforeEach(() => {
    interceptor = new ResponseInterceptor();
  });

  describe("intercept", () => {
    it("should fetch the statuscode and response data", (done: any) => {
      const responseInterception: Observable<any> = interceptor.intercept(
        executionContext,
        nextCallHander
      );

      responseInterception.subscribe({
        next: value => {
          // ...
        },
        error: error => {
          throw error;
        },
        complete: () => {
          done();
        }
      });
    });
  });
});

На этот раз я получаю сообщение об ошибке на перехватчике

TypeError: Невозможно прочитать свойство 'pipe' из неопределенного

Может ли кто-нибудь помочь мне правильно проверить эти два перехватчика?

Заранее спасибо

1 Ответ

2 голосов
/ 07 января 2020

Тестирование перехватчиков может быть одной из самых сложных частей тестирования приложения Nest JS из-за ExecutionContext и возврата правильного значения из next.

Давайте начнем с ExecutionContext:

У вас все в порядке с вашим текущим контекстом, важно то, что у вас есть метод switchToHttp(), если вы используете HTTP (как и вы) и что все, что возвращается switchToHttp(), имеет метод getResponse() или getRequest() (или оба, если используются оба). Оттуда методы getRequest() или getResponse() должны возвращать значения, которые используются из req и res, такие как res.statusCode или req.originalUrl. Мне нравится иметь входящие и исходящие на одном и том же перехватчике, поэтому часто мои context объекты будут выглядеть примерно так:

const context = {
  switchToHttp: jest.fn(() => ({
    getRequest: () => ({
      originalUrl: '/',
      method: 'GET',
      params: undefined,
      query: undefined,
      body: undefined,
    }),
    getResponse: () => ({
      statusCode: 200,
    }),
  })),
  // method I needed recently so I figured I'd add it in
  getType: jest.fn(() => 'http')
}

Это просто делает контекст легким и простым в работе. Конечно, вы всегда можете заменить значения более сложными, как вам нужно для целей регистрации.

Теперь самое интересное - объект CallHandler. CallHandler имеет функцию handle(), которая возвращает наблюдаемое. По крайней мере, это означает, что ваш next объект должен выглядеть примерно так: htis:

const next = {
  handle: () => of()
}

Но это довольно просто c и мало помогает с регистрацией ответов или работой с отображением ответов , Чтобы сделать функцию обработчика более устойчивой, мы всегда можем сделать что-то вроде

const next = {
  handle: jest.fn(() => of(myDataObject)),
}

Теперь, если необходимо, вы можете переопределить функцию через Jest, но в целом этого достаточно. Теперь ваш nest.handle() вернет Observable и будет доступен через Rx JS операторов.

Теперь для тестирования Observable вы почти правы с подпиской, с которой работаете, и это здорово ! Один из тестов может выглядеть следующим образом:

describe('ResponseInterceptor', () => {
  let interceptor: ResponseInterceptor;
  let loggerSpy = jest.spyOn(Logger.prototype, 'debug');

  beforeEach(() => {
    interceptor = new ResponseInterceptor();
  });

  afterEach(() => {
    loggerSpy.resetMock();
  });

  describe('intercept', () => {
    it('should fetch the request object', (done: any) => {
      const responseInterceptor: Observable<any> = interceptor.intercept(executionContext, nextCallHander);

      responseInterceptor.subscribe({
        next: value => {
          // expect the logger to have two parameters, the data, and the intercept function name
          expect(loggerSpy).toBeCalledWith({statusCode: 200, responseData: value}, 'intercept');
        },
        error: error => {
          throw error;
        },
        complete: () => {
          // only logging one request
          expect(loggerSpy).toBeCalledTimes(1);
          done();
        },
      });
    });
  });
});

Где executionContext и callHandler взяты из значений, которые мы установили выше.

Аналогичная идея может быть реализована с помощью RequestInterceptor, но регистрируется только в части complete наблюдателя (обратный вызов подписки), поскольку нет точек данных, возвращаемых по своей сути (хотя это все равно будет работать в любом случае из-за того, как работают наблюдаемые объекты).

Если Вы хотели бы увидеть пример из реальной жизни (хотя и с библиотекой создания макетов), вы можете проверить мой код для пакета журналов, над которым я работаю.

...