justHelloWorld
justHelloWorld

Reputation: 6818

How to test react components when fetch in componentDidMount?

I have component where I perform two fetch operations in componentDidMount. I want to test this and I have to be honest: it's not clear to me how to proceed at all.

The point is that it seems that there isn't a "standard" way to proceed. Essentially what I find more confusing is:

It's not very clear to me when I should use one approach/library instead of the other.

This is a simplified version of my function:

componentDidMount() {
    fetch(URL, {
        method: 'GET',
    }).then(response => {
        if (response.ok) {
            return response.json();
        } else {
            throw new Error("Error loading data from " + URL);
        }
    }).then(data => {
        if (!_.isEmpty(data)) {
            this.setState({
                data: data,
            });
        } else {
            throw new Error("Invalid data from " + URL);
        }
    }).catch(error => {
        console.log(URL + ' error: ', error);
        this.setState({error});
    });

    const payload = {...};

    fetch(URL2, {
        method: 'POST',
        body: JSON.stringify(payload),
    }).then(response => {
        if (response.ok) {
            return response.json();
        } else {
            throw new Error("Error loading data from " + URL2);
        }
    }).then(data => {
        if (!_.isEmpty(data2)) {
            this.setState({
                data2: data2
            });
        } else {
            throw new Error("Invalid data from " + URL2);
        }

    }).catch(error => {
        this.setState({error, isLoading: false});
    });

}

What I want to test is:

Of course I will need a mocking mechanism to mock the two answers (for GET and POST operations) but is not clear how should I do it, or how to test the results.

Upvotes: 5

Views: 8198

Answers (3)

Shivam Latawa
Shivam Latawa

Reputation: 96

We generally test the state that the lifecycle methods have changed by mocking the fetch calls. Avoid using setTimeout in tests as you never know how much time the fetchMock is gonna take, so you can use await instead of that. For example:

import React from "react";
import {shallow} from "enzyme";
import fetchMock from "fetch-mock";
import TestComponent from "./TestComponent";

describe(() => {
    it("should set the state after fetching data", () => {
        // arrange
        fetchMock.get(`https://www.example.com`, mockResponse);
        const wrapper = shallow(<TestComponent>);
        
        // act
        await wrapper.instance().componentDidMount();

        // assert
        expect(wrapper.state()).toEqual(newStateObject);

    })

})

Upvotes: 0

Dmitry Sheiko
Dmitry Sheiko

Reputation: 2192

The trick here is to assert state/snapshot after the data from remote source are received. Even with mocking it's still goes asynchronously. So you can use e.g. setTimeout to postpone assertion:

import React from "react";
import { shallow } from "enzyme";
import sinon from "sinon";
import fetch from "node-fetch";

sinon.stub(fetch, "Promise").returns(
  Promise.resolve({
    json: () => Promise.resolve( { name: "Hello" } )
  })
);


class Test extends React.Component {
  state = {
    name: "none"
  };
  async componentDidMount() {
    const res = await fetch( "https://swapi.co/api/people/1" ),
          data = await res.json();
    this.setState({ name: data.name });
  }
  render() {
    return <h1>{ this.state.name }</h1>;
  }
}

describe( "component with fetch", () => {
  test( "state gets updated with the fetch", ( done ) => {
    const wrapper = shallow( <Test /> );
    setTimeout(() => {
      wrapper.update();
      const state = wrapper.instance().state;
      console.log(state);
      done();
    }, 10 );
  });

});

Upvotes: 0

Jo&#227;o Vila&#231;a
Jo&#227;o Vila&#231;a

Reputation: 621

You don't need to mock the api call. fetch has its own library tests so you don't need to test if fetch works. But if you really need to test your method, you can just use jest - https://facebook.github.io/jest/docs/en/asynchronous.html . Forget the jest-fetch-mock. You can test:

  1. Was the method componentDidMount called?
  2. Was yourMethod called?
  3. After yourMethod finished, did the change occurred? (Your new state is the expected one?)

Just remember not to test the libraries themselves, or to go very deep into the component tree. You should only test atomically. One thing at a time.

Now:

You can use async/await or just test the fetch itself. First of all, you should abstract those fetch'es to their own methods. Now. If all you do is concatenate promises and if u get everything correctly u set the state, you just need to , on the test file, resolve that promise, and on its callback, check if the state changed to what you wanted.

Again, this has all you need to know: https://facebook.github.io/jest/docs/en/asynchronous.html#promises

And if you need one more resource here you go: https://codereviewvideos.com/course/react-redux-and-redux-saga-with-symfony-3/video/testing-javascript-s-fetch-with-jest-happy-path

Upvotes: 3

Related Questions