5

I have built an HttpInterceptor that closely follows the one in the Angular Documentation.

However, I needed to make an asynchronous call as part of my interceptor. I created a StackBlitz with a simplified version of the code (but identical in semantics).

The interceptor looks like:

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;
            })
        );
    }
}

It's fairly simple:

  • check if the URL we are calling requires our token ( req.url.startsWith() )
  • if yes, then fetch our token and add it as a header
  • if not, simply continue the pipeline

The ConfigurationService has a simple string property named apiUrl. The SessionService has an almost equally simple method named getToken() which returns a Promise<string>.

The code works as expected: response with authorization header

However, I am having a hard time testing this...

My actual test is very simple:

it('should add authorization header for API call', () => {
    http.get('bar').subscribe();
    httpMock.expectOne(req => req.headers.has('Authorization'));
});

I have mocked the getToken() and apiUrl properly so that apiUrl='bar' and getToken() returns Promise.resolve('foobar'). The problem seems to be only when going through the addAuth() path. If I test for the counter-case, it works:

it('should NOT add authorization header for non-API call', () => {
    http.get('baz').subscribe();
    httpMock.expectOne(req => !req.headers.has('Authorization'));
});
Eric Liprandi
  • 5,324
  • 2
  • 49
  • 68
  • I've never worked with Observable but the `subscribe` method is async no? It would explain why the header is not available. – Samuel Vaillant Feb 15 '19 at 16:10
  • @SamuelVaillant that's a good point. But I was just following the [documentation](https://v6.angular.io/guide/http#expecting-and-answering-requests). I think it has to do with some unresolved promises... – Eric Liprandi Feb 15 '19 at 16:15
  • So, by wrapping in a `fakeAsync()` and calling `tick()` after the `.subscribe()`, it seems to work. I am going to do some more digging to see if there is a more intuitive way... – Eric Liprandi Feb 15 '19 at 18:30
  • Thanks for the heads up! – Samuel Vaillant Feb 15 '19 at 18:56

1 Answers1

2

So, as mentioned in one of my comments, the fix seems to be the use of fakeAsync() and tick() like this:

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'));
}));

While it makes sense, if anyone could clarify why I need to tick()... I thought that resolving the promise would be enough. I did try using await http.get().toPromise(); (and using the async keyword - not the function), but that didn't work.

Eric Liprandi
  • 5,324
  • 2
  • 49
  • 68