Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Could not test angular http interceptor with Karma and Jasmine

I am trying to test an http interceptor, and I can't find any way to do it.

I don't know why the url doesn't match because my token is logged in the console and the request url is the same too. I tested all of my other http services using this method without the interceptor and everything passes...

Here is the interceptor class, the code works and add my bearer token in every requests, it uses Ionic Platform native class which is mocked in the test:

@Injectable()
export class AddHeaderInterceptor implements HttpInterceptor {

  public constructor(
      private auth: AuthService, private platform: Platform, 
      private config: ConfigurationService
  ) {}

  public intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    console.debug('req', req.url);
    return Observable.fromPromise(this.platform.ready())
      .switchMap(() => Observable.fromPromise(this.auth.getToken()))
      .switchMap((tokens: TokenCacheItem[]) => {
        const baseUrl = new RegExp(this.config.getValue('baseUrl'), 'g');
        if (! req.url.match(baseUrl)) {
          return Observable.of(null);
        }
        if (tokens.length > 0) {
          return Observable.of(tokens[0].accessToken);
        }
        return Observable.throw(null);
      })
      .switchMap((token: string) => {
        console.debug('token', token);
        if (!token || token === null) {
          return next.handle(req);
        }
        const headers = req.headers
          .set('Authorization', `Bearer ${token}`)
          .append('Content-Type', 'application/json');
        const clonedRequest = req.clone({ headers });
        return next.handle(clonedRequest);
    });
  }
}

The token get logged in the karma console, I've also checked the url and always have the good url, but the test failed.

Here is my auth service mock:

export class AuthServiceMock {
  getToken() {
    return Promise.resolve([
      { accessToken: 'TEST' }
    ]);
  }
}

The Platform mock:

export class PlatformMock {
  public ready(): Promise<string> {
    return Promise.resolve('READY');
  }
}

And here is the test:

describe('AddHeaderInterceptor Service', () => {

  let http: HttpTestingController;
  let httpClient: HttpClient;
  let auth: AuthService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [
        ConfigurationService,
        {
          provide: HTTP_INTERCEPTORS,
          useClass: AddHeaderInterceptor,
          multi: true,
        },
        { provide: Platform, useClass: PlatformMock },
        { provide: LogService, useClass: LogServiceMock },
        { provide: AuthService, useClass: AuthServiceMock }
      ],
    });
    http = TestBed.get(HttpTestingController);
    httpClient = TestBed.get(HttpClient);
    auth = TestBed.get(AuthService);
  });

  it('should add Auth header to request', () => {
    httpClient.get('/test').subscribe(res => expect(res).toBeTruthy());
    const req = http.expectOne({ method: 'GET' });
    expect(req.request.url.includes('test')).toBeTruthy();
    expect(req.request.headers.has('Authorization')).toBeTruthy();
    expect(req.request.headers.get('Authorization')).toEqual('Bearer TEST');
    req.flush({
      hello: 'world'
    });
  });
});

I always got the same error :

Error: Expected one matching request for criteria "Match method: GET, URL: (any)", found none.

It looks like the request is never launched, I've tried to create a class instance and call directly the intercept method and I already got the same error. I tried to spy the getTokenmethod from the auth service and it's never called. So I don't know why it failed.

I've tried to call another service with another url and I still got the same error.

I tried to use absolute or relative urls with http.expectOne('http://localhost:8080/test') and it's still the same:

Expected one matching request for criteria "Match URL: http://localhost:8080/test", found none.

Anybody has an idea?

like image 892
Julien METRAL Avatar asked Mar 05 '23 22:03

Julien METRAL


2 Answers

I find a solution using the karma done function and constructing directly the Addheader object:

fdescribe('AddHeaderInterceptor Service', () => {

  let http: HttpTestingController;
  let httpClient: HttpClient;
  let auth: AuthService;
  let logger: LogService;
  let config: ConfigurationService;
  let platform: Platform;
  let interceptor: AddHeaderInterceptor;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [
        ConfigurationService,
        {
          provide: HTTP_INTERCEPTORS,
          useClass: AddHeaderInterceptor,
          multi: true,
        },
        { provide: Platform, useClass: PlatformMocked },
        { provide: LogService, useClass: LogServiceMock },
        { provide: AuthService, useClass: AuthServiceMock }
      ],
    });
    config = TestBed.get(ConfigurationService);
    auth = TestBed.get(AuthService);
    logger = TestBed.get(LogService);
    platform = TestBed.get(Platform);
    httpClient = TestBed.get(HttpClient);
    http = TestBed.get(HttpTestingController);
    interceptor = new AddHeaderInterceptor(logger, auth, platform, config);
  });

  fit('should add header to request', (done) => {
    expect((interceptor as any) instanceof AddHeaderInterceptor).toBeTruthy();
    const next: any = {
      handle: (request: HttpRequest<any>) => {
        expect(request.headers.has('Authorization')).toBeTruthy();
        expect(request.headers.get('Authorization')).toEqual('Bearer TESTO');
        return Observable.of({ hello: 'world' });
      }
    };
    const req = new HttpRequest<any>('GET', config.getValue('baseUrl') + 'test');
    interceptor.intercept(req, next).subscribe(obj => done());
  });

});
like image 126
Julien METRAL Avatar answered Mar 13 '23 15:03

Julien METRAL


I ran into the same scenario when I started using the same fromPromise -> switchMap pattern in my interceptor. That seems to cause the issue with testing (though the code seems to work fine). Without the call to the promise, it works fine.

No idea why - but your testing solution worked for me. Thanks!

like image 32
Chuck Spencer Avatar answered Mar 13 '23 14:03

Chuck Spencer