我有一個使用 Firebase 進行身份驗證的 Angular 專案。為了對我們的服務器進行身份驗證,我們需要來自 Firebase 的 JWT。該專案正在使用 Angular Fire 來實作這一點。
我們有一個 HTTP 攔截器,它攔截對服務器的任何請求,并添加所需的 Authorization 標頭。攔截器似乎作業得很好,但我們也想對其進行單元測驗。
我們的 AuthService 負責將操作委托給 Firebase。該getCurrentUserToken方法將 JWT 作為承諾檢索,因為 Angular Fire 會在需要時重繪 JWT。
這使攔截器稍微復雜化,因為我們需要異步獲取令牌并將其包裝到請求中的 Observable 中。
令牌攔截器.ts
@Injectable()
export class TokenInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) {}
intercept(
req: HttpRequest<unknown>,
next: HttpHandler
): Observable<HttpEvent<unknown>> {
if (req.url.includes(environment.apiRoot)) {
return defer(async () => {
const token = await this.authService.getCurrentUserToken();
const request = req.clone({
headers: this.getDefaultHeaders(token),
withCredentials: true,
});
return next.handle(request);
}).pipe(mergeAll());
} else {
return next.handle(req);
}
}
private getDefaultHeaders(accessToken: string): HttpHeaders {
return new HttpHeaders()
.set(
'Access-Control-Allow-Origin',
`${window.location.protocol}//${window.location.host}`
)
.set(
'Access-Control-Allow-Headers',
'Access-Control-Allow-Origin,Access-Control-Request-Method,Access-Control-Request-Headers,Access-Control-Allow-Headers,Authorization,Accept,Content-Type,Origin,Host,Referer,X-Requested-With,X-CSRF-Token'
)
.set('Content-Type', 'application/json')
.set('Accept', 'application/json')
.set('Authorization', `Bearer ${accessToken}`);
}
}
我們通過HttpClientModule在HTTP_INTERCEPTORS注入令牌下匯入和提供攔截器來在我們的 AppModule 中提供攔截器
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: TokenInterceptor,
multi: true,
},
這個問題只出現在我們的單元測驗中
令牌攔截器.spec.ts
describe(`TokenInterceptor`, () => {
let service: SomeService;
let httpMock: HttpTestingController;
let authServiceStub: AuthService;
beforeEach(() => {
authServiceStub = {
getCurrentUserToken: () => Promise.resolve('some-token'),
} as AuthService;
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: TokenInterceptor,
multi: true,
},
{ provide: AuthService, useValue: authServiceStub },
],
});
service = TestBed.inject(SomeService);
httpMock = TestBed.inject(HttpTestingController);
});
afterEach(() => {
httpMock.verify();
});
it('should add an Authorization header', () => {
service.sendSomeRequestToServer().subscribe((response) => {
expect(response).toBeTruthy();
});
const httpRequest = httpMock.expectOne({});
expect(httpRequest.request.headers.has('Authorization')).toEqual(true);
expect(httpRequest.request.headers.get('Authorization')).toBe(
'Bearer some-token'
);
httpRequest.flush({ message: "SUCCESS" });
httpMock.verify();
});
});
測驗在httpMock.expectOne({})帶有訊息的陳述句處失敗
Error: Expected one matching request for criteria "Match method: (any), URL: (any)", found none.
似乎我們的模擬請求根本沒有發送。
但是,如果我們從 Testbed 中的提供者中洗掉攔截器,則測驗會在 expect 陳述句處失敗,這表明正在找到模擬請求。
Error: Expected false to equal true.
at <Jasmine>
at UserContext.<anonymous> (src/app/http-interceptors/token-interceptor.spec.ts:57:62)
at ZoneDelegate.invoke (node_modules/zone.js/fesm2015/zone.js:372:1)
at ProxyZoneSpec.onInvoke (node_modules/zone.js/fesm2015/zone-testing.js:287:1)
Error: Expected null to be 'Bearer some-token'.
at <Jasmine>
at UserContext.<anonymous> (src/app/http-interceptors/token-interceptor.spec.ts:58:62)
at ZoneDelegate.invoke (node_modules/zone.js/fesm2015/zone.js:372:1)
at ProxyZoneSpec.onInvoke (node_modules/zone.js/fesm2015/zone-testing.js:287:1)
為什么expectOne 找不到我們的服務發送的請求?是攔截器本身的問題還是測驗的構建方式?
uj5u.com熱心網友回復:
我認為問題出在攔截器中,因為我們可能不會在進行斷言之前等待async任務const token = await this.authService.getCurrentUserToken();完成。
在攔截器中添加這樣的console.log:
return defer(async () => {
console.log('[Interceptor] Before token');
const token = await this.authService.getCurrentUserToken();
console.log('[Interceptor] After token');
const request = req.clone({
headers: this.getDefaultHeaders(token),
withCredentials: true,
});
return next.handle(request);
}).pipe(mergeAll());
嘗試使用 fakeAsync 并勾選:
it('should add an Authorization header', fakeAsync(() => {
service.sendSomeRequestToServer().subscribe((response) => {
expect(response).toBeTruthy();
});
// Add a tick here to tell Angular to finish all promises encountered
// in the code before carrying forward with the tests.
tick();
console.log('[Test] Before expectation');
const httpRequest = httpMock.expectOne({});
expect(httpRequest.request.headers.has('Authorization')).toEqual(true);
expect(httpRequest.request.headers.get('Authorization')).toBe(
'Bearer some-token'
);
httpRequest.flush({ message: "SUCCESS" });
httpMock.verify();
}));
如果沒有tick,我懷疑你會看到[Interceptor] Before token然后[Test] Before expectation(錯誤的順序)與你應該看到的勾號[Interceptor] Before token,[Interceptor] After token然后[Test] Before expectation(正確的順序)
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/385171.html
標籤:有角的 打字稿 单元测试 业力茉莉 angular-http-拦截器
