当模拟点击调用调用承诺的函数时,使用 React 的 Jest 和 Enzyme 进行测试
Testing with React's Jest and Enzyme when simulated clicks call a function that calls a promise
- React v15.1.0
- Jest v12.1.1
- 酵素 v2.3.0
我正在尝试弄清楚如何测试在通过单击调用的函数中调用 promise 的组件。我期待 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!');
});
});
在结束测试之前没有太多需要以某种方式等待承诺实现。我可以从您的代码中看到两种主要的方法。
独立测试 onClick
和您的承诺方法。因此,检查 onClick
调用了正确的函数,但监视 setTextWithPromise
,触发点击并断言调用了 setTextWithPromise
。然后你还可以获得组件实例并调用该方法,returns 你可以附加一个处理程序并断言它做了正确的事情。
公开一个你可以传入的回调道具,当承诺解决时调用它。
更新的答案: 使用 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()
在使用此方法时既不充分也不需要,因为 Promises 永远不会在它们创建的同一个时间点内解析——这是设计使然。有关这里发生的事情的非常详细的解释,请参阅 。
原答案:逻辑相同,但略逊一筹。使用 Node 的 setImmediate
to defer the test until the next tick, which is when the promise will resolve. Then call Jest's 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();
})
});
这不是很好,因为如果您必须等待多个 promise,您会得到很大的嵌套回调。
- React v15.1.0
- Jest v12.1.1
- 酵素 v2.3.0
我正在尝试弄清楚如何测试在通过单击调用的函数中调用 promise 的组件。我期待 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!');
});
});
在结束测试之前没有太多需要以某种方式等待承诺实现。我可以从您的代码中看到两种主要的方法。
独立测试
onClick
和您的承诺方法。因此,检查onClick
调用了正确的函数,但监视setTextWithPromise
,触发点击并断言调用了setTextWithPromise
。然后你还可以获得组件实例并调用该方法,returns 你可以附加一个处理程序并断言它做了正确的事情。公开一个你可以传入的回调道具,当承诺解决时调用它。
更新的答案: 使用 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()
在使用此方法时既不充分也不需要,因为 Promises 永远不会在它们创建的同一个时间点内解析——这是设计使然。有关这里发生的事情的非常详细的解释,请参阅
原答案:逻辑相同,但略逊一筹。使用 Node 的 setImmediate
to defer the test until the next tick, which is when the promise will resolve. Then call Jest's 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();
})
});
这不是很好,因为如果您必须等待多个 promise,您会得到很大的嵌套回调。