0

I'm trying to unit test an http interceptor. The interceptor is used for authorization. I can test the basic success case, but I'm trying now to test the case where the API call fails because access token is out of date.

My question is, how do I spy on the AuthInterceptorService.getNewTokenAndRetryRequest() method? My interceptor & unit test code are below. When I run the unit tests, I get a "Error: Expected spy getNewTokenAndRetryRequest to have been called." error.

This is my unit test :

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [
        HttpClientTestingModule
      ],
      providers: [TokenStorageService,
  
      {
          provide: HTTP_INTERCEPTORS,
          useClass: AuthInterceptorService,
          multi: true
      }      
      
      ]

    });
    service = TestBed.inject(AuthInterceptorService);
  });


     it('should proceed to refresh token logic on 500 error', inject([HttpClient, HttpTestingController], (http: HttpClient, httpMock: HttpTestingController) => {
        let  thownError: Error = null;
    
        spyOn(service, "getNewTokenAndRetryRequest").and.stub();
    
    
        Object.defineProperty(window.document, 'cookie', {
          writable: true,
          value: 'access_token=mycookie',
        });
    
        http.get('/data').subscribe(
            response => {
                expect(response).toBeTruthy();
            }
        );
    
    
        const req = httpMock.expectOne(r =>
            r.headers.has('Authorization') && 
            r.headers.get('Authorization').startsWith('Bearer ewogIC') );
    
        req.flush("failure",{ status: 500, statusText: "500 error" });
    
        expect(service.getNewTokenAndRetryRequest).toHaveBeenCalled();
    
        httpMock.verify();
      }));

This is my code for the interceptor

 export class AuthInterceptorService implements HttpInterceptor {

  constructor(private tokenStorageService: TokenStorageService, private httpClient: HttpClient) { }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<any> {
    const updatedHeaders:HttpHeaders = req.headers.set('Authorization', 'Bearer ' + this.tokenStorageService.getAccessToken());
    const updatedRequest = req.clone({headers: updatedHeaders})
    return next.handle(updatedRequest)
    .pipe(  
      catchError((error: HttpErrorResponse) => {
        if (error.status===500) {

          console.log('500 error, try to use the refresh token');
          try{
            this.getNewTokenAndRetryRequest(req);
            return of([]);
          }catch (error) {
            return throwError(error);
          }
        }else{
          let errorMsg =  `Error Code: ${error.status},  Message: ${error.message}`;

          return throwError(error.error);
        }
       
    })
   )
  }

  getNewTokenAndRetryRequest(req: HttpRequest<any>): void {
    //do stuff

  }
 
Mr Smith
  • 3,318
  • 9
  • 47
  • 85

1 Answers1

0

There could be a race condition where we are asserting too early. Try to assert in the subscribe callback.

Try making the following changes (follow !!):

it('should proceed to refresh token logic on 500 error', inject([HttpClient, HttpTestingController], fakeAsync((http: HttpClient, httpMock: HttpTestingController) => {
        let  thownError: Error = null;
    
        // !! Assign this spy to a variable and remove .and.stub();
        const getNewTokenSpy = spyOn(service, "getNewTokenAndRetryRequest");
    
    
        Object.defineProperty(window.document, 'cookie', {
          writable: true,
          value: 'access_token=mycookie',
        });
    
        http.get('/data').subscribe(
            response => {
                expect(response).toBeTruthy();
                // !! move assertion here
                expect(service.getNewTokenAndRetryRequest).toHaveBeenCalled();
                // !! make sure this test below fails so we know this
                // subscribe callback is being traversed.
                // Delete this assertion once it fails.
                expect(1).toBe(2);
            }
        );
    
    
        const req = httpMock.expectOne(r =>
            r.headers.has('Authorization') && 
            r.headers.get('Authorization').startsWith('Bearer ewogIC') );
    
        req.flush("failure",{ status: 500, statusText: "500 error" });
    
        httpMock.verify();
      })));
AliF50
  • 16,947
  • 1
  • 21
  • 37
  • I tried this, but getNewTokenAndRetryRequest is not mocked. If im setting up AuthInterceptorService ( the class being tested) in beforeEach, how can I add a mock in the IT function? – Mr Smith Jan 16 '23 at 16:16
  • If you want it to actually be called, you have to do `spyOn(service, 'getNewTokenAndRetryRequest').and.callThrough();`. This way the method will actually be called. Without the `.and.callThrough();`, the method won't be called but we know if it was called and how many times. – AliF50 Jan 16 '23 at 17:53