Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I test a component created by asynchronous call in componentDidMount?

I'm making a GET request to my API http://localhost:3001/api/cards from the componentDidMount function of a component, so that the api request is made only after the component is rendered for the first time (As suggested by react official guide).

This API sets the state of an array data. In render function, I call data.map function to render multiple components from this array. How should I test whether the desired number of components have been rendered?

My component:

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

Now I want to check if there are as many Card components being rendered as specified by my API.

I tried this by first mocking the fetch function to return 1 element. Then I use Full DOM Rendering of enzyme and mount the above component and expect it to contain 1 element.

Test case:

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

All the tests are passing except that it can't find Card element. Even the fetch mock function is called. It fails until I put a setTimeout function before I try to find Card component.

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

Is there any concept I'm failing to understand? How should I ideally test such asynchronously loading components? How can I better design them to be easily testable? Any help would be greatly appreciated. Thanks

like image 250
Ayush Avatar asked Jan 07 '17 10:01

Ayush


1 Answers

You have to wait for the resolved promise of your fetch result and for the promise from the parseJSON. Therefor we need to mock parseJSON and let it return a resolved promise as well. Please note that the path needs to be relative to the test file.

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);
});
like image 192
Andreas Köberle Avatar answered Oct 11 '22 02:10

Andreas Köberle