我继承了许多集成测试,这些测试针对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
的调用仍在进行中。因此,第二次测试可能会显示不稳定的结果。
我想知道对此可能有什么适当的回应。一些建议的解决方案包括:
模拟setTimeout
,setInterval
,requestAnimationFrame
并排队其回调,而不是异步执行。允许回调的同步播放。十分神奇且容易出错的解决方案,承诺仍是异步的,需要单独处理
重写生产代码,以确保公开所有未完成的承诺。这使得使用async
和await
更具挑战性和直观性。
setTimeout
,setInterval
,requestAnimationFrame
和Promise
上的 Spy中,尝试推断出由于方法运行而导致哪些异步行为仍在等待处理中,通过轮询间谍来确定异步分辨率。这没有上面的复杂,但是竞争条件和第三方代码变得更难以调试,并且不清楚如何使用await
或yield
。
也许还有其他选择?基本上,不清楚是否应该适当地公开异步功能是应用程序的责任,还是负担完全由测试环境承担。如果在测试环境中,完全控制异步行为是否有意义,或者是严格采用反动方法?
TL; DR:鼠标/触摸事件处理程序是异步的,不需要公开其内部结构。为了支持测试需求,什么是适当的响应?