【问题标题】:How can I test a component created by asynchronous call in componentDidMount?如何在 componentDidMount 中测试异步调用创建的组件?
【发布时间】:2017-05-22 01:59:42
【问题描述】:

我正在从组件的 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>
        );
    }
};

现在我想检查是否有与我的 API 指定的一样多的 Card 组件正在呈现。

我首先模拟 fetch 函数以返回 1 个元素。然后我使用全 DOM 渲染酶和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);
});

除了找不到 Card 元素外,所有测试都通过了。甚至调用了 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);
    });
}

有什么我不明白的概念吗?我应该如何理想地测试这种异步加载组件?我怎样才能更好地将它们设计成易于测试?任何帮助将不胜感激。谢谢

【问题讨论】:

  • 你真的需要await parseJSON吗?你没有包括那个来源,但我猜它不是异步的。 MDN 文档说“如果值不是承诺,它会将值转换为已解决的承诺,并等待它。”所以看起来它应该可以工作,但也许这种方法和测试框架会有所帮助?
  • 对不起,我没有包含 parseJSON 的来源。它基本上在 fetch API 调用返回的 Response 对象上调用 .json()。我需要等待它,因为它返回一个承诺。 developer.mozilla.org/en-US/docs/Web/API/Body/json
  • 在这种情况下,我认为解决方案是 await BOTH 承诺(fetchparseJSON)。
  • 使用await (await result).json();,我得到TypeError: Already read。那是因为 body 已经被 .json() 读取过了,不能再读取了。 stackoverflow.com/questions/34786358/…
  • 你这里有两个 Promise,所以都需要解决。您正在模拟 fetch 以返回已解决的承诺,因此这可能没问题(以及下面 Andreas 的等待),但您似乎需要模拟 parseJSON 方法,例如parseJSONPromise = Promise.resolve({"id": "1234"});parseJSON = jest.fn().mockImplementation(() =&gt; parseJSONPromise);await parseJSONPromise;expect(parseJSON.).toBeCalled();

标签: javascript unit-testing reactjs jestjs enzyme


【解决方案1】:

您必须 wait 获取已解决的获取结果的承诺以及来自 parseJSON 的承诺。因此我们需要模拟 parseJSON 并让它返回一个已解决的承诺。请注意,路径需要相对于测试文件。

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);
});

【讨论】:

  • 如果没有 setTimeoutP,它就无法工作。我什至 update() 最后一行的包装器
  • 我认为如果你也添加一个模拟出来的parseJSON,你的答案会起作用。来自 OP:await parseJSON(await fetch(...
  • 啊,你说得对,这会让测试很难跟上,会更新答案
猜你喜欢
  • 2016-11-13
  • 1970-01-01
  • 2018-12-23
  • 2020-03-15
  • 2020-11-26
  • 2020-02-12
  • 2020-01-20
  • 2019-01-13
  • 1970-01-01
相关资源
最近更新 更多