期待多年,你终于可以购买谷歌最热门的新产品gShoe*了。但在点击“购买”按钮后,什么也没发生!检查HTML代码,你发现问题所在:
<button disabled=”true” click=”$handleBuyClick(data)”>Buy</button>
由于“购买”按钮禁用,用户无法购买gShoes。问题出在handleBuyClick,尽管在用户界面上存在BUG,但它的单元测试仍然通过了。
it('submits purchase request', () => {
controller = new PurchasePage();
// 调用处理“购买”按钮点击的方法
controller.handleBuyClick(data);
expect(service).toHaveBeenCalledWith(expectedData);
});
在上面的例子中,测试未能发现这个bug, 是因为绕过了UI元素,而不直接调用“购买”按钮的点击事件处理程序。为了有效测试,对UI逻辑的测试,应该像浏览器一样,与页面上的组件进行交互,从而可以测试最终用户所体验到的行为。编写针对UI组件的测试,而不是直接调用处理程序,可以更真实地模拟用户交互(例如,将物品添加到购物车,点击购买按钮,或者验证页面上的元素是否可见),使测试更全面。
对”购买”按钮的测试,应该通过与HTML元素进行交互,来测试整个UI组件,这样就可以发现禁用按钮的问题:
it('submits purchase request', () => {
// 渲染“购买”按钮及其相关代码的页面
render(PurchasePage);
// 尝试点击按钮,测试失败,捕获到bug!
buttonWithText('Buy').dispatchEvent(new Event(‘click’));
expect(service).toHaveBeenCalledWith(expectedData);
});
为什么要这样编写测试用例呢?与端到端测试不同,针对单个UI组件不需要渲染后端服务或整个应用。相反,这些测试用例在同一个封闭环境中运行,执行时间与直接执行底层事件处理程序的单元测试相似。因此,UI充当公共API,而将业务逻辑作为实现细节(也称为“先用前门”原理),从而更好地覆盖功能。