Testing a method that is subscribed to an observable - Angular 2

13,670

Solution 1

Ok so it's taken me most of the day but I finally cracked it. Instead of using the injectAsync and TestComponentBuilder to set up the spec I just need to use inject and inject the component in just like you do a service. This seems fine because I don't need to test anything in the view like events.

Heres the final spec that does work:

it('Should set token in localStorage, set the new user, 
and navigate to home page on succesful login', 
  inject([Login], (login) => {
    login.router.config([ { path: '/', name: 'Home', component: Home }]);
    spyOn(localStorage, 'setItem');
    spyOn(login._currentUser, 'set');
    spyOn(login.router, 'navigate');
    login.onSubmit({ userId: '[email protected]', password: 'password', siteName: 'sample' });
    expect(localStorage.setItem).toHaveBeenCalledWith('token', 'newToken');
    expect(login._currentUser.set).toHaveBeenCalledWith({ 'test': 'one' });
    expect(login.router.navigate).toHaveBeenCalledWith(['/Home']);
}));

Hope this might help someone in the future.

Solution 2

I guess you want to inject a mock Router instance to your component and then after navigate(['/Home']) was called on the mock Router, you check if localStorage.setItem(...) was called.

Share:
13,670
HomeBrew
Author by

HomeBrew

Updated on June 22, 2022

Comments

  • HomeBrew
    HomeBrew almost 2 years

    I want to test a method inside of an Angular 2 component that is subscribed to an observable that is returned from a method in a service. Here is the code for the service method in summary:

    public create(user: User): Observable<any> {
      return this.http.post(this._api.create,
        JSON.stringify(user), {
          headers: this.apiConfig.getApiHeaders()
        }).map((res: Response) => res.json());
      }
    

    It's easy to unit test this method because it returns an observable so I can just subscribe to it. But I want to test the method in the component that is already subscribed to this:

    public onSubmit(user: User): void {
      this._authentication.create(user).subscribe((token) => {
        localStorage.setItem('token', token);
        this.router.navigate(['/Home']);
      });
    }
    

    Heres my spec so far but when I try to spyOn the localStorage.setItem it comes back as not being called. My understanding is it's probably checking to see if it's been called before it's actually been called.

    it('Should login a user and on success store a token in localStorage',
      injectAsync([TestComponentBuilder], (tcb) => {
        return tcb.createAsync(Login).then((fixture) => {
          let instance = fixture.debugElement.componentInstance;
          localStorage.clear();
          spyOn(localStorage, 'setItem');
          instance.onSubmit({userId: '[email protected]', password: 'password', siteName: 'sample'});
          expect(localStorage.setItem).toHaveBeenCalled();
        });
      })
    );
    

    I'm wondering if I need to mock out the this._authentication.create method to return a new observable with a mock response in it?

    After more research a few articles indicated that I do need to mock out the service and return an Observable.of() which runs synchronously to solve the problem, ill copy the code below. This however still doesn't work, I've been working on this most of the day, I don't feel this should be that hard, any help appreciated.

    class MockAuthentication extends Authentication {
      public create(user: Object): Observable<any> {
        return Observable.of({'test': 'test'});
      }
    }
    
  • HomeBrew
    HomeBrew about 8 years
    It feels like the check would happen before they got called because of the subscribe but I'm new to Observables. Also, we used to just use spyOn(localStorage, 'setItem') in Angular 1 but I'm having a hard time figuring out how to do the same with Angular 2.
  • HomeBrew
    HomeBrew about 8 years
    My bad, looks like you still use spyOn the same, my problem was I couldn't figure out where you need to import 'spyOn' from but it seems you just have it. But it seems my statement about the check happening before still runs true. I've updated my question to include the spec.
  • Elias Garcia
    Elias Garcia about 7 years
    This makes no sense.. Why are you calling spyOn in a service mock? If it's mocked, it's also returning a mock value, you don't need to return another mock value with the returnValue Jasmine function.
  • Chris Pawlukiewicz
    Chris Pawlukiewicz over 6 years
    @EliasGarcia because you are determining if the function logic calls the dependency. The spy is optional.