如何测试使用 Angular 6+ 调用可观察对象的拦截器?
how do I test an interceptor which calls an observable with Angular 6+?
我已经构建了一个 HttpInterceptor
,它与 Angular 文档中的那个非常相似。
但是,我需要进行异步调用作为拦截器的一部分。我用代码的简化版本创建了一个 StackBlitz(但在语义上相同)。
拦截器看起来像:
export class AuthInterceptor implements HttpInterceptor {
constructor(private session: SessionService, private config: ConfigurationService) {}
intercept(req: HttpRequest<any>, next: HttpHandler) {
const apiRoot = this.config.apiUrl;
if (apiRoot && req.url.startsWith(apiRoot)) {
return this.addAuth(req).pipe(switchMap(x => next.handle(x)));
} else {
return next.handle(req);
}
}
private addAuth(original: HttpRequest<any>): Observable<HttpRequest<any>> {
return from(this.session.getToken()).pipe(
map(token => {
const req = original.clone({
setHeaders: { Authorization: `Bearer ${token}` }
});
return req;
})
);
}
}
这很简单:
- 检查我们调用的 URL 是否需要我们的令牌 (
req.url.startsWith()
)
- 如果是,则获取我们的令牌并将其添加为 header
- 如果没有,只需继续管道
ConfigurationService
有一个名为 apiUrl
的简单 string
属性。
SessionService
有一个几乎同样简单的方法,名为 getToken()
which returns a Promise<string>
.
代码按预期工作:
但是,我很难测试这个...
我的实测很简单:
it('should add authorization header for API call', () => {
http.get('bar').subscribe();
httpMock.expectOne(req => req.headers.has('Authorization'));
});
我已经正确地嘲笑了 getToken()
和 apiUrl
,所以 apiUrl='bar'
和 getToken()
returns Promise.resolve('foobar')
。
通过 addAuth()
路径时,问题似乎是 仅 。如果我测试反例,它会起作用:
it('should NOT add authorization header for non-API call', () => {
http.get('baz').subscribe();
httpMock.expectOne(req => !req.headers.has('Authorization'));
});
所以,正如我的一条评论中提到的,解决方法似乎是像这样使用 fakeAsync()
和 tick()
:
beforeEach(()=>{
spyOn(sessionService, 'getToken').and.returnValue(Promise.resolve('foobar'));
});
it('should add authorization header for API call', fakeAsync(() => {
http.get('bar').subscribe();
tick();
httpMock.expectOne(req => req.headers.has('Authorization'));
}));
虽然这是有道理的,但如果有人能阐明我为什么需要 tick()
... 我认为 解决 承诺就足够了。我确实尝试过使用 await http.get().toPromise();
(并使用 async
关键字 - 而不是函数),但这没有用。
我已经构建了一个 HttpInterceptor
,它与 Angular 文档中的那个非常相似。
但是,我需要进行异步调用作为拦截器的一部分。我用代码的简化版本创建了一个 StackBlitz(但在语义上相同)。
拦截器看起来像:
export class AuthInterceptor implements HttpInterceptor {
constructor(private session: SessionService, private config: ConfigurationService) {}
intercept(req: HttpRequest<any>, next: HttpHandler) {
const apiRoot = this.config.apiUrl;
if (apiRoot && req.url.startsWith(apiRoot)) {
return this.addAuth(req).pipe(switchMap(x => next.handle(x)));
} else {
return next.handle(req);
}
}
private addAuth(original: HttpRequest<any>): Observable<HttpRequest<any>> {
return from(this.session.getToken()).pipe(
map(token => {
const req = original.clone({
setHeaders: { Authorization: `Bearer ${token}` }
});
return req;
})
);
}
}
这很简单:
- 检查我们调用的 URL 是否需要我们的令牌 (
req.url.startsWith()
) - 如果是,则获取我们的令牌并将其添加为 header
- 如果没有,只需继续管道
ConfigurationService
有一个名为 apiUrl
的简单 string
属性。
SessionService
有一个几乎同样简单的方法,名为 getToken()
which returns a Promise<string>
.
代码按预期工作:
但是,我很难测试这个...
我的实测很简单:
it('should add authorization header for API call', () => {
http.get('bar').subscribe();
httpMock.expectOne(req => req.headers.has('Authorization'));
});
我已经正确地嘲笑了 getToken()
和 apiUrl
,所以 apiUrl='bar'
和 getToken()
returns Promise.resolve('foobar')
。
通过 addAuth()
路径时,问题似乎是 仅 。如果我测试反例,它会起作用:
it('should NOT add authorization header for non-API call', () => {
http.get('baz').subscribe();
httpMock.expectOne(req => !req.headers.has('Authorization'));
});
所以,正如我的一条评论中提到的,解决方法似乎是像这样使用 fakeAsync()
和 tick()
:
beforeEach(()=>{
spyOn(sessionService, 'getToken').and.returnValue(Promise.resolve('foobar'));
});
it('should add authorization header for API call', fakeAsync(() => {
http.get('bar').subscribe();
tick();
httpMock.expectOne(req => req.headers.has('Authorization'));
}));
虽然这是有道理的,但如果有人能阐明我为什么需要 tick()
... 我认为 解决 承诺就足够了。我确实尝试过使用 await http.get().toPromise();
(并使用 async
关键字 - 而不是函数),但这没有用。