NestJs请求和响应拦截器单元测试

时间:2020-01-07 07:43:25

标签: jestjs nestjs

我想记录我的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:无法读取未定义的属性“ subscribe”

我还尝试测试响应拦截器,并提出了

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:无法读取未定义的属性“管道”

请问有什么思路可以帮助我正确地测试这两个拦截器?

预先感谢

1 个答案:

答案 0 :(得分:4)

由于ExecutionContext并从next返回正确的值,因此测试拦截器可能是测试NestJS应用程序中最具挑战性的部分之一。

让我们从ExecutionContext开始:

您已经对当前上下文进行了正确设置,重要的是,如果您正在使用HTTP(就像您一样),则可以使用switchToHttp()方法,而{{1 }}具有switchToHttp()getResponse()方法(或同时使用两者)。从那里,getRequest()getRequest()方法应该返回从req和res中使用的值,例如getResponse()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

但这是非常基本的,对于记录响应或使用响应映射没有太大帮助。为了使处理程序功能更强大,我们总是可以做类似的事情

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

现在,如果需要,您可以通过Jest覆盖该功能,但是通常就足够了。现在,您的const next = { handle: jest.fn(() => of(myDataObject)), } 将返回一个Observable,并且可以通过RxJS运算符进行点入。

现在可以测试Observable了,您就可以使用正在处理的订阅了,这太好了!其中一项测试如下所示:

next.handle()

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部分(订阅回调),因为没有固有返回的数据点(尽管仍然可以使用)由于可观察性如何工作,两种方式都可以。)

如果您想看到一个真实的示例(尽管有一个模拟创建库),则可以check out my code来获取我正在处理的日志记录包。