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

Antonis Christofides picture Antonis Christofides · Aug 1, 2017 · Viewed 12.2k times · Source

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()?

Answer

Crysfel picture Crysfel · Aug 1, 2017

I'd separate concerns, mainly because is easier to maintain and to test. Instead of declaring the fetch inside the component I'd do it somewhere else, for example in a redux action (if using redux).

Then test individually the fetch and the component, after all this is unit testing.

For async tests you can use the done parameter on the test. For example:

describe('Some tests', () => {
  fetchMock.get('*', some_response);

  it('should fetch data', (done) => { // <---- Param
    fetchSomething({ some: 'Params' })
      .then(result => {
        expect(result).toBe({ whatever: 'here' });
        done(); // <--- When you are done
      });
  });
})

The you can tests your component by just sending the loaded data in the props.

describe('MyComponent', () => {

  it('renders something', () => {
    const mockResponse = { some: 'data' };
    let wrapper = mount(<MyComponent data={mockResponse}/>);

    expect(wrapper.find(...)).to.have.something();
  });
});

When it comes to testing you need to keep it simple, if your component is difficult to test, then there's something wrong with your design ;)