我想记录我的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:无法读取未定义的属性“管道”
请问有什么思路可以帮助我正确地测试这两个拦截器?
预先感谢
答案 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来获取我正在处理的日志记录包。