集成测试redux-observable,使用debounce进行多个ajax请求

时间:2018-01-07 23:35:39

标签: rxjs redux-observable

我有一个redux-observable epic,用于轮询端点,获取进度更新,直到进度为100%。使用debounceTime实现轮询间隔,如下所示:

function myEpic(action$, store, dependencies) {
  return action$.ofType('PROCESSING')
    .do(action => console.log(`RECEIVED ACTION: ${JSON.stringify(action)}`))
    .debounceTime(1000, dependencies.scheduler)
    .mergeMap(action => (
      dependencies.ajax({ url: action.checkUrl })
        .map((resp) => {
          if (parseInt(resp.progress, 10) === 100) {
            return { type: 'SUCCESS' };
          }
          return { checkUrl: resp.check_url, progress: resp.progress, type: 'PROCESSING' };
        })));
}

这很好但是我想编写一个集成测试,当进度为25%,然后是50%,然后是100%时,测试商店的状态。

在我的集成测试中,我可以将dependencies.scheduler设置为new VirtualTimeScheduler()

这就是我现在尝试这样做的方式(使用jest):

  describe('my integration test', () => {

    const scheduler = new VirtualTimeScheduler();

    beforeEach(() => {

      // Fake ajax responses
      const ajax = (request) => {

        console.log(`FAKING REQUEST FOR URL: ${request.url}`);

        if (request.url === '/check_url_1') {
          return Observable.of({ progress: 25, check_url: '/check_url_2' });
        } else if (request.url === '/check_url_2') {
          return Observable.of({ progress: 50, check_url: '/check_url_3' });
        } else if (request.url === '/check_url_3') {
          return Observable.of({ progress: 100 });
        }
        return null;
      };
      store = configureStore(defaultState, { ajax, scheduler });
    });

    it('should update the store properly after each call', () => {
      store.dispatch({ checkUrl: '/check_url_1', progress: 0, type: 'PROCESSING' });

      scheduler.flush();
      console.log('CHECK CORRECT STATE FOR PROGRESS 25');

      scheduler.flush();
      console.log('CHECK CORRECT STATE FOR PROGRESS 50');

      scheduler.flush();
      console.log('CHECK CORRECT STATE FOR PROGRESS 100');
    });
  });

我的预期输出是:

RECEIVED ACTION: {"checkUrl":"/check_url_1","progress":0,"type":"PROCESSING"}
FAKING REQUEST FOR URL: /check_url_1
CHECK CORRECT STATE FOR PROGRESS 25
RECEIVED ACTION: {"checkUrl":"/check_url_2","progress":25,"type":"PROCESSING"}
FAKING REQUEST FOR URL: /check_url_2
CHECK CORRECT STATE FOR PROGRESS 50
RECEIVED ACTION: {"checkUrl":"/check_url_3","progress":50,"type":"PROCESSING"}
# CHECK CORRECT STATE FOR PROGRESS 100

但我获得的输出是

RECEIVED ACTION: {"checkUrl":"/check_url_1","progress":0,"type":"PROCESSING","errors":null}
FAKING REQUEST FOR URL: /check_url_1
RECEIVED ACTION: {"checkUrl":"/check_url_2","progress":25,"type":"PROCESSING","errors":null}
CHECK CORRECT STATE FOR PROGRESS 25%
CHECK CORRECT STATE FOR PROGRESS 50%
CHECK CORRECT STATE FOR PROGRESS 100%

此时测试结束。我正在配置商店,以便我可以像推荐的here一样模拟ajax请求和用于debounceTime的调度程序

所以我的问题是如何在三个ajax请求中的每一个之后测试我的商店的状态?

2 个答案:

答案 0 :(得分:2)

有趣的是,我玩了你的代码并相当自信你刚刚在debounceTime运算符中发现了一个错误,导致明显吞噬预定的去抖动。坏消息是,即使修复了这个错误,你的代码仍然不会按顺序执行。

忍耐我,因为狗屎即将成真:

  • Epic接收动作处理并安排去抖动,让执行到你的测试
  • 您的测试呼叫scheduler.flush()和VirtualScheduler执行预定的去抖动工作,该工作会将原始处理操作传递给mergeMap
  • 伪造ajax,同步发出响应
  • 响应映射到第二个PROCESSING操作
  • 你的史诗同步发出第二个动作
  • 第二个动作是由你的史诗递归收到并给予去抖动
  • debounceTime运算符现在计划对VirtualScheduler执行第二个操作,但debounceTime运算符正在执行先前计划的工作
  • 从第一个操作开始。
  • 调用堆栈展开一堆,直到它从第一个动作(刚刚接下来的第一个动作)进入先前计划的去抖动工作。 debounceTime rxjs代码然后设置this.lastValue = null and this.hasValue = false这是rxjs错误,需要在进入目的地之前完成
  • 堆栈会向VirtualScheduler的which now dequeues the second scheduled debounced action的正在运行的flush()方法展开更多内容,因为它已经同步添加了计划的工作数组,在此之前刷新完成。请记住,到目前为止我们只调用了scheduler.flush() ONCE,这是我们此时所处的功能。
  • 第二次计划的去抖动工作正在运行,但是this.hasValue === false因为第一个预定的去抖动设置它,所以debounceTime运算符不会发出任何内容。
  • Stack展开我们的第一个scheduler.flush()
  • 我们console.log('CHECK CORRECT STATE FOR PROGRESS 25')
  • 所有其他scheduler.flush()来电都没有,因为没有安排任何事情。

这在技术上是一个错误,但是没有人遇到它并不奇怪,因为同步地运行去抖动而没有任何延迟使得它失败,除非你正在测试,当然This ticket is basically the same thing和OJ说RxJS没有提供重入保证,但在这种情况下我可能会讨论。 I've filed a PR with the fix to discuss

请记住,这个错误不会解决你关于排序的潜在问题,但是会阻止这些行为被吞噬。

如果你想保持100%的同步行为(VirtualScheduler),我不知道如何做你想要做的事情。在去抖之间你需要一些屈服于你的测试的方法。对我来说当我编写集成测试的时候,如果有的话,我会嘲笑的很少。例如让debounces自然地或者通过模拟setTimeout来更快地推进它们,但是仍然保持它们异步,这将导致你的测试结果允许你检查状态,但是你的测试也是异步的。 / p>

对于任何想要复制的人,here's the StackBlitz code I used

答案 1 :(得分:0)

答案是异步重写测试。同样值得注意的是,我必须通过返回Observable.fromPromise而不仅仅是常规Observable.of来模拟ajax请求,否则他们仍然会被去抖动吞噬。沿着这些方向的东西(使用jest):

describe('my integration test', () => {

  const scheduler = new VirtualTimeScheduler();

  beforeEach(() => {

    // Fake ajax responses
    const ajax = request => (
      Observable.fromPromise(new Promise((resolve) => {
        if (request.url === '/check_url_1') {
          resolve({ response: { progress: 25, check_url: '/check_url_2' } });
        } else if (request.url === '/check_url_2') {
          resolve({ response: { progress: 50, check_url: '/check_url_3' } });
        } else {
          resolve({ response: { progress: 100 } });
        }
      }))
    );

    store = configureStore(defaultState, { ajax, timerInterval: 1 });
  });

  it('should update the store properly after each call', (done) => {

    let i = 0;
    store.subscribe(() => {
      switch (i) {
        case 0:
          console.log('CHECK CORRECT STATE FOR PROGRESS 0');
          break;
        case 1:
          console.log('CHECK CORRECT STATE FOR PROGRESS 25');
          break;
        case 2:
          console.log('CHECK CORRECT STATE FOR PROGRESS 50');
          break;
        case 3:
          console.log('CHECK CORRECT STATE FOR PROGRESS 100');
          done();
          break;
        default:
      }
      i += 1;
    });

    store.dispatch({ checkUrl: '/check_url_1', progress: 0, type: 'PROCESSING' });
  });
});

我还通过将定时器间隔作为依赖项将其设置为1。在我的史诗中我设置如下:.debounceTime(dependencies.timerInterval || 1000)