当模拟点击调用调用 promise 的函数时,使用 React 的 Jest 和 Enzyme 进行测试
Posted
技术标签:
【中文标题】当模拟点击调用调用 promise 的函数时,使用 React 的 Jest 和 Enzyme 进行测试【英文标题】:Testing with React's Jest and Enzyme when simulated clicks call a function that calls a promise 【发布时间】:2016-09-21 09:17:41 【问题描述】: 反应 v15.1.0 Jest v12.1.1 酶 v2.3.0我试图弄清楚如何测试一个在单击调用的函数中调用承诺的组件。我期待 Jest 的 runAllTicks()
函数在这里帮助我,但它似乎没有执行承诺。
组件:
import React from 'react';
import Promise from 'bluebird';
function doSomethingWithAPromise()
return new Promise((resolve) =>
setTimeout(() =>
resolve();
, 50);
);
export default class AsyncTest extends React.Component
constructor(props)
super(props);
this.state =
promiseText: '',
timeoutText: ''
;
this.setTextWithPromise = this.setTextWithPromise.bind(this);
this.setTextWithTimeout = this.setTextWithTimeout.bind(this);
setTextWithPromise()
return doSomethingWithAPromise()
.then(() =>
this.setState( promiseText: 'there is text!' );
);
setTextWithTimeout()
setTimeout(() =>
this.setState( timeoutText: 'there is text!' );
, 50);
render()
return (
<div>
<div id="promiseText">this.state.promiseText</div>
<button id="promiseBtn" onClick=this.setTextWithPromise>Promise</button>
<div id="timeoutText">this.state.timeoutText</div>
<button id="timeoutBtn" onClick=this.setTextWithTimeout>Timeout</button>
</div>
);
还有测试:
import AsyncTest from '../async';
import shallow from 'enzyme';
import React from 'react';
jest.unmock('../async');
describe('async-test.js', () =>
let wrapper;
beforeEach(() =>
wrapper = shallow(<AsyncTest />);
);
// FAIL
it('displays the promise text after click of the button', () =>
wrapper.find('#promiseBtn').simulate('click');
jest.runAllTicks();
jest.runAllTimers();
wrapper.update();
expect(wrapper.find('#promiseText').text()).toEqual('there is text!');
);
// PASS
it('displays the timeout text after click of the button', () =>
wrapper.find('#timeoutBtn').simulate('click');
jest.runAllTimers();
wrapper.update();
expect(wrapper.find('#timeoutText').text()).toEqual('there is text!');
);
);
【问题讨论】:
您可以改为将 doSomethingWithAPromise 函数作为道具传递给 AsyncTest 组件,以便您可以在测试中模拟它:***.com/questions/38308214/… 【参考方案1】:更新答案: 使用 async
/ await
会导致代码更简洁。下面是旧代码。
我通过结合以下元素成功解决了这个问题:
模拟出承诺并立即解决 通过标记测试函数async
使测试异步
模拟点击后,等到下一个macrotask给promise时间解决
在您的示例中,可能如下所示:
// Mock the promise we're testing
global.doSomethingWithAPromise = () => Promise.resolve();
// Note that our test is an async function
it('displays the promise text after click of the button', async () =>
wrapper.find('#promiseBtn').simulate('click');
await tick();
expect(wrapper.find('#promiseText').text()).toEqual('there is text!');
);
// Helper function returns a promise that resolves after all other promise mocks,
// even if they are chained like Promise.resolve().then(...)
// Technically: this is designed to resolve on the next macrotask
function tick()
return new Promise(resolve =>
setTimeout(resolve, 0);
)
Enzyme 的update()
在使用此方法时既不够用也不需要,因为 Promise 永远不会在它们创建的同一滴答中解析——这是设计使然。有关此处发生的事情的详细说明,请参阅this question。
原答案:相同的逻辑,但稍微不那么漂亮。使用 Node 的 setImmediate
将测试推迟到下一个滴答声,也就是 Promise 将解决的时间。然后调用 Jest 的done
异步完成测试。
global.doSomethingWithAPromise = () => Promise.resolve();
it('displays the promise text after click of the button', (done) =>
wrapper.find('#promiseBtn').simulate('click');
setImmediate( () =>
expect(wrapper.find('#promiseText').text()).toEqual('there is text!');
done();
)
);
这不太好,因为如果你必须等待多个承诺,你会得到很大的嵌套回调。
【讨论】:
宾果游戏!setImmediate
是我需要在我的回调函数中使用的,它是通过我的组件的 componentDidUpdate
生命周期方法之一触发的。
我遇到了类似的问题,但我用setTimeout(() => ... , 0)
解决了这个问题。出于某种原因,setImmediate
使测试失败,甚至yarn test
命令也突然退出。
谢谢你!我在 setImmediate 之外调用了 done()
,但是这种组合有效并且可以防止测试套件挂起。
该解决方案看起来很有希望,但在我的情况下 setImmediate 没有任何效果。承诺被简单地忽略,其后果不被等待。我是否需要模拟我想要等待的 Promise,还是还有一种方法可以简单地等待内部使用的 Promise?
setImmediate()
将不起作用,除非立即解决承诺。所以你可能想模拟它。【参考方案2】:
在结束测试之前不需要以某种方式等待承诺实现。从你的代码中我可以看到有两种主要的方法。
独立测试onClick
和你的promise 方法。因此检查onClick
是否调用了正确的函数,但监视setTextWithPromise
,触发点击并断言setTextWithPromise
被调用。然后,您还可以获取组件实例并调用返回承诺的方法,您可以附加处理程序并断言它做了正确的事情。
公开一个你可以传入的回调 prop,当 Promise 解决时调用它。
【讨论】:
以上是关于当模拟点击调用调用 promise 的函数时,使用 React 的 Jest 和 Enzyme 进行测试的主要内容,如果未能解决你的问题,请参考以下文章