如何测试在 componentDidMount 中异步调用创建的组件?
How can I test a component created by asynchronous call in componentDidMount?
我正在从组件的 componentDidMount 函数向我的 API http://localhost:3001/api/cards
发出 GET 请求,因此 api 请求仅在为第一次(根据react官方指南的建议)。
这个 API 设置数组的状态 data
。在渲染函数中,我调用 data.map
函数来渲染这个数组中的多个组件。我应该如何测试是否已经渲染了所需数量的组件?
我的组件:
//CardGrid.js
import React from 'react';
import { Card, Col, Row } from 'antd';
import 'antd/dist/antd.css';
import { parseJSON } from './commonfunction';
import './CardGrid.css';
export default class extends React.Component {
constructor()
{
super();
this.state = {
data: {},
};
}
fetchData = async () => {
try
{
const data = await parseJSON(await fetch('http://localhost:3001/api/cards'));
console.log('data');
console.log(data);
this.setState({ data });
}
catch (e)
{
console.log('error is: ');
console.log(e);
}
}
componentDidMount() {
this.fetchData();
}
render() {
return (
<div style={{ background: '#ECECEC', padding: '30px' }}>
<Row gutter={16}>
{Object.keys(this.state.data).map((title) => {
return (<Col span="6" key={title}>
<Card title={title} bodyStyle={{
'fontSize': '6em',
}}>{this.state.data[title]}</Card>
</Col>);
})}
</Row>
</div>
);
}
};
现在我想检查是否有 Card
个组件正在渲染,正如我的 API 所指定的那样。
我通过首先将 fetch
函数模拟为 return 1 个元素来尝试此操作。然后我使用 Full DOM Rendering of enzyme 和 mount
上述组件并期望它包含 1 个元素。
测试用例:
// It fails
import React from 'react';
import { Card } from 'antd';
import { mount } from 'enzyme';
import CardGrid from './CardGrid';
it('renders 1 Card element', () => {
fetch = jest.fn().mockImplementation(() =>
Promise.resolve(mockResponse(200, null, '{"id":"1234"}')));
const wrapper = mount(<CardGrid />);
expect(fetch).toBeCalled();
expect(wrapper.find(CardGrid).length).toEqual(1);
expect(wrapper.find(Card).length).toEqual(1);
});
除了找不到卡片元素外,所有测试都通过了。甚至 fetch 模拟函数也会被调用。它失败了,直到我在尝试查找 Card 组件之前放置了一个 setTimeout 函数。
//It succeeds
import React from 'react';
import { Card } from 'antd';
import { mount } from 'enzyme';
import sinon from 'sinon';
import CardGrid from './CardGrid';
it('renders 1 Card elements', async () => {
fetch = jest.fn().mockImplementation(() =>
Promise.resolve(mockResponse(200, null, '{"id":"1234"}')));
const wrapper = mount(<CardGrid />);
expect(fetch).toBeCalled();
expect(wrapper.find(CardGrid).length).toEqual(1);
await setTimeoutP();
expect(wrapper.find(Card).length).toEqual(1);
});
function setTimeoutP () {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log('111111111');
resolve();
}, 2000);
});
}
有什么概念我不明白吗?我应该如何理想地测试这种异步加载的组件?我怎样才能更好地设计它们以便于测试?任何帮助将不胜感激。谢谢
您必须 wait 获得已解决的获取结果承诺和来自 parseJSON
的承诺。因此,我们需要模拟 parseJSON
并让它 return 成为一个已解决的承诺。请注意,路径需要相对于测试文件。
import {parseJSON} from './commonfunction'
jest.mock('./commonfunction', () => {parseJSON: jest.fn()}) //this will replace parseJSON in the module by a spy were we can later on return a resolved promise with
it('renders 1 Card elements', async () => {
const result = Promise.resolve(mockResponse(200, null, '{"id":"1234"}'))
parsedResult = Promise.resolve({"id":"1234"})
parseJSON.mockImplementation(()=>parsedResult)
fetch = jest.fn(() => result)
const wrapper = mount(<CardGrid />);
await result;
await parsedResult;
expect(fetch).toBeCalled();
expect(wrapper.find(CardGrid).length).toEqual(1);
expect(wrapper.find(Card).length).toEqual(1);
});
我正在从组件的 componentDidMount 函数向我的 API http://localhost:3001/api/cards
发出 GET 请求,因此 api 请求仅在为第一次(根据react官方指南的建议)。
这个 API 设置数组的状态 data
。在渲染函数中,我调用 data.map
函数来渲染这个数组中的多个组件。我应该如何测试是否已经渲染了所需数量的组件?
我的组件:
//CardGrid.js
import React from 'react';
import { Card, Col, Row } from 'antd';
import 'antd/dist/antd.css';
import { parseJSON } from './commonfunction';
import './CardGrid.css';
export default class extends React.Component {
constructor()
{
super();
this.state = {
data: {},
};
}
fetchData = async () => {
try
{
const data = await parseJSON(await fetch('http://localhost:3001/api/cards'));
console.log('data');
console.log(data);
this.setState({ data });
}
catch (e)
{
console.log('error is: ');
console.log(e);
}
}
componentDidMount() {
this.fetchData();
}
render() {
return (
<div style={{ background: '#ECECEC', padding: '30px' }}>
<Row gutter={16}>
{Object.keys(this.state.data).map((title) => {
return (<Col span="6" key={title}>
<Card title={title} bodyStyle={{
'fontSize': '6em',
}}>{this.state.data[title]}</Card>
</Col>);
})}
</Row>
</div>
);
}
};
现在我想检查是否有 Card
个组件正在渲染,正如我的 API 所指定的那样。
我通过首先将 fetch
函数模拟为 return 1 个元素来尝试此操作。然后我使用 Full DOM Rendering of enzyme 和 mount
上述组件并期望它包含 1 个元素。
测试用例:
// It fails
import React from 'react';
import { Card } from 'antd';
import { mount } from 'enzyme';
import CardGrid from './CardGrid';
it('renders 1 Card element', () => {
fetch = jest.fn().mockImplementation(() =>
Promise.resolve(mockResponse(200, null, '{"id":"1234"}')));
const wrapper = mount(<CardGrid />);
expect(fetch).toBeCalled();
expect(wrapper.find(CardGrid).length).toEqual(1);
expect(wrapper.find(Card).length).toEqual(1);
});
除了找不到卡片元素外,所有测试都通过了。甚至 fetch 模拟函数也会被调用。它失败了,直到我在尝试查找 Card 组件之前放置了一个 setTimeout 函数。
//It succeeds
import React from 'react';
import { Card } from 'antd';
import { mount } from 'enzyme';
import sinon from 'sinon';
import CardGrid from './CardGrid';
it('renders 1 Card elements', async () => {
fetch = jest.fn().mockImplementation(() =>
Promise.resolve(mockResponse(200, null, '{"id":"1234"}')));
const wrapper = mount(<CardGrid />);
expect(fetch).toBeCalled();
expect(wrapper.find(CardGrid).length).toEqual(1);
await setTimeoutP();
expect(wrapper.find(Card).length).toEqual(1);
});
function setTimeoutP () {
return new Promise(function (resolve, reject) {
setTimeout(() => {
console.log('111111111');
resolve();
}, 2000);
});
}
有什么概念我不明白吗?我应该如何理想地测试这种异步加载的组件?我怎样才能更好地设计它们以便于测试?任何帮助将不胜感激。谢谢
您必须 wait 获得已解决的获取结果承诺和来自 parseJSON
的承诺。因此,我们需要模拟 parseJSON
并让它 return 成为一个已解决的承诺。请注意,路径需要相对于测试文件。
import {parseJSON} from './commonfunction'
jest.mock('./commonfunction', () => {parseJSON: jest.fn()}) //this will replace parseJSON in the module by a spy were we can later on return a resolved promise with
it('renders 1 Card elements', async () => {
const result = Promise.resolve(mockResponse(200, null, '{"id":"1234"}'))
parsedResult = Promise.resolve({"id":"1234"})
parseJSON.mockImplementation(()=>parsedResult)
fetch = jest.fn(() => result)
const wrapper = mount(<CardGrid />);
await result;
await parsedResult;
expect(fetch).toBeCalled();
expect(wrapper.find(CardGrid).length).toEqual(1);
expect(wrapper.find(Card).length).toEqual(1);
});