Search code examples
javascriptangularunit-testingjestjsangular-spectator

Jest Async tests are confusing me


Here is the scenario:

Using Jest/Spectator to test RXJS observable but can't seem to get to the line of code I want to test via my current setup

Component code -

  ngOnInit(): void {  
    this.authDetail$ = this.validateToken(this.token).pipe(
      takeUntil(this.unsubscribe$),
      catchError((error) => { 
        if (error) {
          // I want to test this next line...
          // But I never see it run...
          this.router.navigate(['unauthorized'], { replaceUrl: true });
        }
        // This only exists to satisfy the observable chain.
        return of({} as SomeModel);
      }),
    );
  }

  validateToken(token: string): Observable<SomeModel> {
    return this.authService.lookupByToken(token);
  }

Test-

  it('should redirect to "unauthorized" when error is thrown', (done) => {

      jest.spyOn(spectator.component, 'validateToken')
        .mockReturnValue(throwError({ status: 403 }) as any);

      spectator.component.validateToken('invalid_token').subscribe({
        next: (data) => {
          console.log('NEXT BLOCK: Should Have Thrown Error');
          done();
        },
        error: (error) => {
          expect(spectator.router.navigate).toHaveBeenCalledWith(
            ['unauthorized'],
            { replaceUrl: true },
          );
          expect(error).toBeTruthy(); 
          done();
        },
      });

      // This fires off the ngOnInit :)
      spectator.setRouteParam('token', 'INVALID_TOKEN');
    });

The issue I am having is that when the test runs I can see that I am receiving a 403 but the router.navigate is not being called. If I console.log that part of the subscribe block, in the component, I see that it's never reached.

How do I test that line of code?


Solution

  • I think I see your problem.

    If you have:

    catchError((error) => { 
            if (error) {
              // I want to test this next line...
              // But I never see it run...
              this.router.navigate(['unauthorized'], { replaceUrl: true });
            }
            // This only exists to satisfy the observable chain.
            return of({} as SomeModel);
          }),
    

    The return of(.. will make it go to the success block and not the error block when you subscribe to that RxJS stream because the catchError is saying if there is an error, handle it this way and return this (of(..) for the stream.

    I see you're expecting the navigate call in the error part of the stream.

    I would try changing the test to this:

    it('should redirect to "unauthorized" when error is thrown', (done) => {
    
          jest.spyOn(spectator.component, 'validateToken')
            .mockReturnValue(throwError({ status: 403 }) as any);
    
          // This fires off the ngOnInit :)
          spectator.setRouteParam('token', 'INVALID_TOKEN');
          // subscribe to authDetail$ after it has been defined in ngOnInit
          spectator.component.authDetail$.pipe(take(1)).subscribe({
             next: (result) => {
               expect(spectator.router.navigate).toHaveBeenCalledWith(
                ['unauthorized'],
                { replaceUrl: true },
              );
              expect(result).toBeTruthy();
              done();
             }
          });
        });