我有一个ReactJS组件,它做两件事:-在ComponentDidMount上,它将检索一个条目列表-在按钮单击它将提交选择条目到一个后端
问题是,我需要模拟这两个请求(用fetch发出),以便正确地测试它。在我当前的测试用例中,我希望在单击submit按钮时测试失败。然而,由于一些奇怪的原因,setState被触发了,但是,在我想比较它之后,会收到来自它的更新。
我在测试时做过的。第一种是考试中听的状态。第二个是代码本身,它将state().error设置为从调用中接收的错误。
FAIL react/src/components/Authentication/DealerSelection.test.jsx (6.689s)
● Console
console.log react/src/components/Authentication/DealerSelection.test.jsx:114
{ loading: true,
error: null,
options: [ { key: 22, value: 22, text: 'Stationstraat 5' } ] }
console.log react/src/components/Authentication/DealerSelection.jsx:52
set error to: my error实际测试代码:
it('throws error message when dealer submit fails', done => {
const mockComponentDidMount = Promise.resolve(
new Response(JSON.stringify({"data":[{"key":22,"value":"Stationstraat 5"}],"default":22}), {
status: 200,
headers: { 'content-type': 'application/json' }
})
);
const mockButtonClickFetchError = Promise.reject(new Error('my error'));
jest.spyOn(global, 'fetch').mockImplementation(() => mockComponentDidMount);
const element = mount(<DealerSelection />);
process.nextTick(() => {
jest.spyOn(global, 'fetch').mockImplementation(() => mockButtonClickFetchError);
const button = element.find('button');
button.simulate('click');
process.nextTick(() => {
console.log(element.state()); // state.error null even though it is set with setState but arrives just after this log statement
global.fetch.mockClear();
done();
});
});
});这是我实际使用的组件:
import React, { Component } from 'react';
import { Form, Header, Select, Button, Banner } from '@omnius/react-ui-elements';
import ClientError from '../../Error/ClientError';
import { fetchBackend } from './service';
import 'whatwg-fetch';
import './DealerSelection.scss';
class DealerSelection extends Component {
state = {
loading: true,
error: null,
dealer: '',
options: []
}
componentDidMount() {
document.title = "Select dealer";
fetchBackend(
'/agent/account/dealerlist',
{},
this.onDealerListSuccessHandler,
this.onFetchErrorHandler
);
}
onDealerListSuccessHandler = json => {
const options = json.data.map((item) => {
return {
key: item.key,
value: item.key,
text: item.value
};
});
this.setState({
loading: false,
options,
dealer: json.default
});
}
onFetchErrorHandler = err => {
if (err instanceof ClientError) {
err.response.json().then(data => {
this.setState({
error: data.error,
loading: false
});
});
} else {
console.log('set error to', err.message);
this.setState({
error: err.message,
loading: false
});
}
}
onSubmitHandler = () => {
const { dealer } = this.state;
this.setState({
loading: true,
error: null
});
fetchBackend(
'/agent/account/dealerPost',
{
dealer
},
this.onDealerSelectSuccessHandler,
this.onFetchErrorHandler
);
}
onDealerSelectSuccessHandler = json => {
if (!json.error) {
window.location = json.redirect; // Refresh to return back to MVC
}
this.setState({
error: json.error
});
}
onChangeHandler = (event, key) => {
this.setState({
dealer: event.target.value
});
}
render() {
const { loading, error, dealer, options } = this.state;
const errorBanner = error ? <Banner type='error' text={error} /> : null;
return (
<div className='dealerselection'>
<Form>
<Header as="h1">Dealer selection</Header>
{ errorBanner }
<Select
label='My dealer'
fluid
defaultValue={dealer}
onChange={this.onChangeHandler}
maxHeight={5}
options={options}
/>
<Button
primary
fluid
onClick={this.onSubmitHandler}
loading={loading}
>Select dealer</Button>
</Form>
</div>
);
}
}
export default DealerSelection;发布于 2019-03-14 03:13:40
有趣的是,这只花了一小段时间才找到它。
来自Node.js文档on process.nextTick()的相关部分
从技术上讲,
process.nextTick()不是事件循环的一部分。相反,无论事件循环的当前阶段如何,都将在当前操作完成后处理nextTickQueue。 在给定阶段调用...any时,传递给process.nextTick()的所有回调都将在事件循环继续之前得到解决。
换句话说,节点一旦完成当前操作,就开始处理nextTickQueue,并且在继续执行事件循环之前,它将一直持续到队列为空。
这意味着,如果在process.nextTick()进行处理时调用nextTickQueue,则回调将添加到队列中,并在事件循环继续之前进行处理。
医生警告说:
这可能会造成一些不好的情况,因为
process.nextTick()允许您通过进行递归的 calls来“饿死”I/O,从而阻止事件循环到达轮询阶段。
事实证明,...and也可以饥饿您的Promise回调:
test('Promise and process.nextTick order', done => {
const order = [];
Promise.resolve().then(() => { order.push('2') });
process.nextTick(() => {
Promise.resolve().then(() => { order.push('7') });
order.push('3'); // this runs while processing the nextTickQueue...
process.nextTick(() => {
order.push('4'); // ...so all of these...
process.nextTick(() => {
order.push('5'); // ...get processed...
process.nextTick(() => {
order.push('6'); // ...before the event loop continues...
});
});
});
});
order.push('1');
setTimeout(() => {
expect(order).toEqual(['1','2','3','4','5','6','7']); // ...and 7 gets added last
done();
}, 0);
});因此,在本例中,记录element.state()的嵌套element.state()回调在之前运行,Promise回调将state.error设置为'my error'。
正因为如此,doc建议如下:
我们建议开发人员在所有情况下都使用
setImmediate(),因为这样更容易解释
如果您将process.nextTick调用更改为setImmediate (并将fetch模拟创建为函数,这样Promise.reject()就不会立即运行并导致错误),那么您的测试就会像预期的那样工作:
it('throws error message when dealer submit fails', done => {
const mockComponentDidMount = () => Promise.resolve(
new Response(JSON.stringify({"data":[{"key":22,"value":"Stationstraat 5"}],"default":22}), {
status: 200,
headers: { 'content-type': 'application/json' }
})
);
const mockButtonClickFetchError = () => Promise.reject(new Error('my error'));
jest.spyOn(global, 'fetch').mockImplementation(mockComponentDidMount);
const element = mount(<DealerSelection />);
setImmediate(() => {
jest.spyOn(global, 'fetch').mockImplementation(mockButtonClickFetchError);
const button = element.find('button');
button.simulate('click');
setImmediate(() => {
console.log(element.state()); // state.error is 'my error'
global.fetch.mockClear();
done();
});
});
});发布于 2019-03-06 17:10:28
更新状态需要几个异步调用,所以您的process.nextTick()是不够的。要更新状态,需要这样做:
fetch,获得承诺拒绝,并运行错误处理程序。setState,它将状态更新排队(setState是异步的!)简而言之,您需要等待更长时间才能断言状态。
在没有嵌套process.nextTick()调用的情况下“等待”的一个有用的成语是定义一个测试助手。
function wait() {
return new Promise((resolve) => setTimeout(resolve));
}然后再做
await wait();测试代码中所要求的次数。请注意,这要求您将测试函数定义为
test(async () => {
})而不是
test(done => {
})https://stackoverflow.com/questions/54984438
复制相似问题