集成测试异步DOM事件处理程序

时间:2019-07-09 23:29:40

标签: javascript asynchronous testing

我继承了许多集成测试,这些测试针对DOM元素模拟了鼠标/触摸事件,并在执行结果操作时定期断言应用程序状态。

这些测试中的一个普遍问题是无法等待异步行为的完全完成,从而导致意外的副作用。

请考虑以下内容:

const LoadState = {
  Pending: 'pending',
  Loaded: 'loaded',
  Failed: 'failed'
};

const App = {
  state: {
    loadState: LoadState.Pending,
    renderAnimationFrameId: null
  },
  
  ui: {
    loadState: document.getElementById('load-state'),
    loadButton: document.getElementById('load-button')
  },
  
  initialize() {
    this._load = this._load.bind(this);
    this.ui.loadButton.addEventListener('click', this._load);
  },
  
  render() {
    // Throttle rendering to framerate.
    if (this.state.renderAnimationFrameId) {
      return;
    }
  
    this.state.renderAnimationFrameId = requestAnimationFrame(() => {
      this.ui.loadState.textContent = this.state.loadState;
      this.state.renderAnimationFrameId = null;
    });
  },
  
  _load() {
    window.fetch(new Request('flowers.jpg')).then(({ ok }) => {
      this.state.loadState = ok ? LoadState.Loaded : LoadState.Failed;
    }).catch(() => {
      this.state.loadState = LoadState.Failed;
    }).finally(() => {
      this.render();
    });
  }
};

App.initialize();
App.render();
<input id='load-button' type="button" value="Load Data" />

<div id='load-state'>
  Pending
</div>

假设模拟了fetch的实现,我想验证load-button对被单击的反应是否正确,以及App之后是否正确更新。这样的东西写得很直观:

window.fetch = Promise.resolve(true);
describe('App', () => {
    App.initialize();

    it('should load', (done) => {
        App.ui.loadButton.dispatchEvent(new Event('click'));

        // Wait for event to update state after fetch resolution.
        setTimeout(() => {
            expect(App.state.loadState === LoadState.Loaded);
            done();
        });
    });

    it('should render', (done) => {
        App.render();

        // Wait for throttled render
        setTimeout(() => {
            expect(App.state.renderAnimationFrameId === null);
            expect(App.ui.loadState.textContent === LoadState.Loaded);
            done();
        });
    });
});

这些测试引入了一个细微的错误。第一个测试已部分等待(但未完全等待)App的异步逻辑建立。测试通过是因为state.loadState是正确的,但对render的调用仍在进行中。因此,第二次测试可能会显示不稳定的结果。

我想知道对此可能有什么适当的回应。一些建议的解决方案包括:

  • 模拟setTimeoutsetIntervalrequestAnimationFrame并排队其回调,而不是异步执行。允许回调的同步播放。十分神奇且容易出错的解决方案,承诺仍是异步的,需要单独处理

  • 重写生产代码,以确保公开所有未完成的承诺。这使得使用asyncawait更具挑战性和直观性。

  • setTimeoutsetIntervalrequestAnimationFramePromise上的
  • Spy中,尝试推断出由于方法运行而导致哪些异步行为仍在等待处理中,通过轮询间谍来确定异步分辨率。这没有上面的复杂,但是竞争条件和第三方代码变得更难以调试,并且不清楚如何使用awaityield

也许还有其他选择?基本上,不清楚是否应该适当地公开异步功能是应用程序的责任,还是负担完全由测试环境承担。如果在测试环境中,完全控制异步行为是否有意义,或者是严格采用反动方法?

TL; DR:鼠标/触摸事件处理程序是异步的,不需要公开其内部结构。为了支持测试需求,什么是适当的响应?

0 个答案:

没有答案