Jest 异步测试让我感到困惑

Jest Async tests are confusing me

场景如下:

使用 Jest/Spectator 测试 RXJS observable,但似乎无法到达我想通过当前设置测试的代码行

组件代码-

  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);
  }

测试-

  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');
    });

我遇到的问题是,当测试运行时,我可以看到我收到了 403,但没有调用 router.navigate。如果我 console.log 订阅块的那部分,在组件中,我看到它从未到达。

如何测试该行代码?

我想我明白你的问题了。

如果你有:

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);
      }),

当你订阅那个 RxJS 流时,return of(.. 会让它进入成功块而不是错误块,因为 catchError 是说如果有错误,就这样处理return 这个 (of(..) 用于流。

我看到你在流的错误部分期待导航调用。

我会尝试将测试更改为:

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();
         }
      });
    });