Search code examples
reactjsjestjsfetch-apijest-fetch-mock

How to unit test a React component that renders after fetch has finished?


I'm a Jest/React beginner. In jest's it I need to wait until all promises have executed before actually checking.

My code is similar to this:

export class MyComponent extends Component {
    constructor(props) {
        super(props);
        this.state = { /* Some state */ };
    }

    componentDidMount() {
        fetch(some_url)
            .then(response => response.json())
            .then(json => this.setState(some_state);
    }

    render() {
        // Do some rendering based on the state
    }
}

When the component is mounted, render() runs twice: once after the constructor runs, and once after fetch() (in componentDidMount()) finishes and the chained promises finish executing).

My testing code is similar to this:

describe('MyComponent', () => {

    fetchMock.get('*', some_response);

    it('renders something', () => {
        let wrapper = mount(<MyComponent />);
        expect(wrapper.find(...)).to.have.something();
    };
}

Whatever I return from it, it runs after the first time render() executes but before the second time. If, for example, I return fetchMock.flush().then(() => expect(...)), the returned promise executes before the second call to render() (I believe I can understand why).

How can I wait until the second time render() is called before running expect()?


Solution

  • I found a way to do what I originally asked. I have no opinion (yet) whether it is good strategy or not (in fact I had to refactor the component immediately afterwards, so this question is no longer relevant to what I'm doing). Anyway, here is the testing code (explanation below):

    import React from 'react';
    import { mount } from 'enzyme';
    import { MyComponent } from 'wherever';
    import fetchMock from 'fetch-mock';
    
    let _resolveHoldingPromise = false;
    
    class WrappedMyComponent extends MyComponent {
    
        render() {
            const result = super.render();
            _resolveHoldingPromise && _resolveHoldingPromise();
            _resolveHoldingPromise = false;
            return result;
        }
    
        static waitUntilRender() {
            // Create a promise that can be manually resolved
            let _holdingPromise = new Promise(resolve =>
                _resolveHoldingPromise = resolve);
    
            // Return a promise that will resolve when the component renders
            return Promise.all([_holdingPromise]);
        }
    }
    
    describe('MyComponent', () => {
    
        fetchMock.get('*', 'some_response');
    
        const onError = () => { throw 'Internal test error'; };
    
        it('renders MyComponent appropriately', done => {
            let component = <WrappedMyComponent />;
            let wrapper = mount(component);
            WrappedMyComponent.waitUntilRender().then(
                () => {
                    expect(wrapper.find('whatever')).toBe('whatever');
                    done();
                },
                onError);
        });
    });
    

    The main idea is that, in the testing code, I subclass the component (if this was Python I'd probably monkey-patch it, which works more or less the same way in this case) so that its render() method sends a signal that it executed. The way to send the signal is by manually resolving a promise. When a promise is created, it creates two functions, resolve and reject, which when called terminate the promise. The way to have code outside the promise resolve the promise is by having the promise store a reference to its resolve function in an external variable.

    Thanks to fetch-mock author Rhys Evans who kindly explained the manually-resolve-promise trick to me.