Search code examples
angular6angular-test

unable to mock HttpClient in Jasmine and Karma using HttpTestController


I am testing AuthService which sends user login info to server using another HelperService.

public authServiceSigninUser(user:UserSigninInfo):any{
    console.log('In authServiceSigninUser. contacting server at '+this.API_URL +this.SIGNIN_USER_URL +" with user data "+user+ " with httpOptions "+httpOptions.withCredentials + ","+httpOptions.headers ); //TODOM password should be sent in encrypted format.

    let signinInfo= new UserSigninAPI(user);
    let body = JSON.stringify(signinInfo);
    return this.helperService.sendMessage(this.SIGNIN_USER_URL,body,httpOptions)
  }

I am trying to test the authServiceSigninUser method as follows but when I run the spec, I get error TypeError: Cannot read property 'subscribe' of undefined. It seems that the Observable.Why? The test spec is

describe('authServiceSigninUser test suite',()=>{
  beforeEach(()=>{
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [AuthService, HelperService]
    });
  });
  fit('should sign in user',()=>{
    let spy:any;
    let helper = TestBed.get(HelperService);
    console.log("helper services is ",helper);
    let authService = TestBed.get(AuthService);
    console.log("auth services is ",authService);
    let userSignIn = new UserSigninInfo("test@test.com","test");
    let httpMock = TestBed.get(HttpTestingController);
    spyOn(helper,'sendMessage');
    const responseData = { result: 'success', ['additional-info']: 'login success' };
    let httpEvent:HttpResponse<any> = new HttpResponse<any>({body:responseData});


    let observable:Observable<HttpEvent<any>> = authService.authServiceSigninUser(userSignIn);
    console.log("observable ",observable);//this is undefined
    let subscription = observable.subscribe((event)=>{ //error here
      console.log('event from authService',event);
    });
    const mockReq:TestRequest = httpMock.expectOne(environment.apiUrl+environment.signinUserUrl); //Expect that a single request has been made which matches the given URL, and return its mock
    //once mocking of sending request is done, mock receiving a response. This will trigger the logic inside subscribe function
    mockReq.flush(httpEvent); //flush method provides dummy values as response
    httpMock.verify();//verify checks that there are no outstanding requests;

    expect(helper.sendMessage).toHaveBeenCalled();
  });
});

sendMessage in HelperService is

sendMessage(url:string, body:any,httpOptions):Observable<HttpEvent<any>> {
    this.loaderService.show();
    let observable:Observable<HttpEvent<any>> = this.http.post<any>(url,body,httpOptions);
    return observable.pipe( 
      tap((httpEvent:HttpEvent<any>) => {//tap transparently perform actions or side-effects, such as logging.
        if(httpEvent.type === HttpEventType.Response)
        {
          console.log('response from backend service:', httpEvent);
        }
        else {
          console.log("not an http response")
        }
        return httpEvent;
      })
      ,catchError(err=>this.handleError(err)) 
      ,finalize(()=> this.loaderService.hide())); 
  }

Solution

  • I don't know why the above test doesn't work but I think that my approach in general about testing isn't correct. As I am unit testing AuthService, I probably should mock the responses I expect from methods of dependent services. Eg, I should spyOn sendMessage and return a mock value. Also my object should be to test that sendMessage was called with correct arguments.

    describe('authServiceSigninUser test suite',()=>{
      beforeEach(()=>{
        TestBed.configureTestingModule({
          imports: [HttpClientTestingModule],//need this as HelperSerice needs HttpClient and this module provides it. I could have also used HttpClientModule
          providers: [AuthService, HelperService]
        });
      });
      fit('should send request to sign in user',()=>{
       // let spy:any;
        let helper = TestBed.get(HelperService);
        //console.log("helper services is ",helper);
        let authService = TestBed.get(AuthService);
        //console.log("auth services is ",authService);
        let userSignIn = new UserSigninInfo("test@test.com","test"); //data to send to server
    
    //response expected from sendMessage
        const responseData = { result: 'success', ['additional-info']: 'login success' };
        let httpEvent:HttpResponse<any> = new HttpResponse<any>({body:responseData});
        spyOn(helper,'sendMessage').and.returnValue(new Observable(()=>{
          httpEvent
        })); //when sendMessage is called, return the mocked response
    
        let observable:Observable<HttpEvent<any>> = authService.authServiceSigninUser(userSignIn); //now when authServiceSignInUserr is called, it will call sendMessage and the spyOn will return mocked response
        console.log("observable returned ",observable);
        let subscription = observable.subscribe((event)=>{ 
          console.log('event from authService',event);
        });
    
    //check that the arguments passed to sendMessage are correct
            let APIToBeUsed = authService.SIGNIN_USER_URL;
            let JSONToBeUsed = JSON.stringify(new UserSigninAPI(userSignIn));
            let HTTPOptionsToBeUsed = authService.httpOptions;
    
    
    
    expect(helper.sendMessage).toHaveBeenCalledWith(APIToBeUsed,jasmine.any(String),jasmine.any(Object));
            expect(helper.sendMessage).toHaveBeenCalledWith(jasmine.any(String),JSONToBeUsed,jasmine.any(Object));
            expect(helper.sendMessage).toHaveBeenCalledWith(jasmine.any(String),jasmine.any(String),HTTPOptionsToBeUsed);
    
          });
        });