首页 > 解决方案 > 单元测试 NestJS Observable Http Retry

问题描述

我正在通过 NestJS 内置的 HttpService 向第 3 方 API 发出请求。我正在尝试模拟一个场景,其中对该 api 端点之一的初始调用可能会在第一次尝试时返回一个空数组。我想retryWhen在延迟 1 秒后使用 RxJS 再次访问 api。但是,我目前无法获得单元测试来模拟第二个响应:

it('Retries view account status if needed', (done) => {
    jest.spyOn(httpService, 'post')
      .mockReturnValueOnce(of(failView)) // mock gets stuck on returning this value
      .mockReturnValueOnce(of(successfulView));
    const accountId = '0812081208';
    const batchNo = '39cba402-bfa9-424c-b265-1c98204df7ea';
    const response =client.viewAccountStatus(accountId, batchNo);
    response.subscribe(
      data => {
        expect(data[0].accountNo)
          .toBe('0812081208');
        expect(data[0].companyName)
          .toBe('Some company name');
        done();
      },
    )
  });

我的实现是:

viewAccountStatus(accountId: string, batchNo: string): Observable<any> {
    const verificationRequest = new VerificationRequest();
    verificationRequest.accountNo = accountId;
    verificationRequest.batchNo = batchNo;


    this.logger.debug(`Calling 3rd party service with batchNo: ${batchNo}`);

    const config = {
      headers: {
        'Content-Type': 'application/json',
      },
    };

    const response = this.httpService.post(url, verificationRequest, config)
      .pipe(
        map(res => {
          console.log(res.data); // always empty
          if (res.status >= 400) {
            throw new HttpException(res.statusText, res.status);
          }

          if (!res.data.length) {
            this.logger.debug('Response was empty');
            throw new HttpException('Account not found', 404);
          }

          return res.data;
        }),
        retryWhen(errors => {
          this.logger.debug(`Retrying accountId: ${accountId}`);
          // It's entirely possible the first call will return an empty array
          // So we retry with a backoff
          return errors.pipe(
            delayWhen(() => timer(1000)),
            take(1),
          );
        }),
      );

    return response;
  }

从初始地图内部登录时,我可以看到该数组始终为空。就好像第二个模拟值永远不会发生。也许我对可观察对象的工作方式也有一个严重的误解,我应该以某种方式尝试断言发出的 SECOND 值?无论如何,当 observable 重试时,我们应该看到第二个模拟值,对吧?

我也越来越

: Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Timeout - Async callback was not invoked within the 5000ms timeout specified by jest.setTimeout.Error:

每次跑步......所以我猜我没有done()在正确的地方打电话。

标签: rxjsjestjsnestjs

解决方案


我认为问题在于它在发出retryWhen(notifier)时会重新订阅同一个源。notifier

这意味着如果你有

new Observable(s => {
  s.next(1);
  s.next(2);

  s.error(new Error('err!'));
}).pipe(
  retryWhen(/* ... */)
)

每次重新订阅源时都会调用回调。在您的示例中,它将调用负责发送请求的逻辑,但不会post再次调用该方法。

源可以被认为是Observable的回调:s => { ... }

我认为您必须做的是根据是否发生错误有条件地选择来源。

也许你可以使用mockImplementation

let hasErr = false;

jest.spyOn(httpService, 'post')
  .mockImplementation(
    () => hasErr ? of(successView) : (hasErr = true, of(failView))
  )

编辑

我认为上面没有做任何不同的事情,我认为mockImplementation应该是这样的:

let err = false;

mockImplementation(
 () => new Observable(s => {
   if (err) { 
     s.next(success) 
   } 
   else { 
    err = true;
    s.next(fail) 
   } 
 })
)

推荐阅读