Rober
Rober

Reputation: 19

How to unit test nested service methods with subscriptions in Angular

I have a service class look like this, and trying to add unit test cases which I'm not able figure out, how to test the nested methods with the subscriptions.

TestService {
  constructor(@Inject(ENV) private env,
              private userService: UserService,
              private addressService: AddressService) {}

  public getDetails(): void {
    this.getUser().subscribe((user) => {
      if (user && user !== null) {
        this.addressService.getAddress(user).subscribe((address) => {
          this.addressService.updateAddress(user).subscribe(() => {
          
          });
        });
      }
    });
  }

  private getUser(): Observable<string> {
    return userService.findUser().pipe(map((response: any) => response.output.user), take(1));
  }
}

tried few scenario by spying on the method and returning some mock data but did not work.

here what I tried, and the expectation is if the user is null, I should not have been called getAddress() from addressService, it's giving me error,

it('get user should not call if user is null', async(() => {
  service.getDetails();
  spyOn(userService, 'getUser').and.returnValue(of('TEST'));
  const addressServiceSpy = spyOn(addressService, 'getAddress');
  expect(addressServiceSpy).toHaveBeenCalledWith('TEST');
}));

Error:

Expected: 'TEST' Number of calls: 0

Upvotes: 1

Views: 605

Answers (1)

Owen Kelvin
Owen Kelvin

Reputation: 15083

I believe you are finding it difficult to test your code because of excessive complexity. It is a good practice NEVER TO NEST SUBSCRIPTIONS

Lets try to break your code into small chunks for easy testing

  private getUser(): Observable<string> {
    return userService.findUser().pipe(
     map(({output}) => output.user), 
     take(1));
  }
  public getDetails(): Observable<any> {
    return this.getUser().pipe(
      mergeMap(user => user ? forkJoin([
        this.addressService.getAddress(user), 
        this.addressService.updateAddress(user)]) : of(null)),
      map(([address]) => address)
    )
  }

Now we can simply subscribe to the observable in the test

it('get user should not call if user is null', async(() => {
  spyOn(userService, 'getUser').and.returnValue(of(null));
  service.getDetails().subscribe({
    next: res => {
      expect(res).toBeNull();
    }
  });
  
}));

Upvotes: 0

Related Questions