How to TEST async calls made in componentDidMount that set the state of React Component How to TEST async calls made in componentDidMount that set the state of React Component reactjs reactjs

How to TEST async calls made in componentDidMount that set the state of React Component


So, what you are really trying to test is that based on some mock data it "should render correctly ...".

As some people pointed out, a good way to achieve that is by placing the data fetching logic into a separate container and have a "dumb" presentation component that only knows how to render props.

Here is how to do that:(I had to modify it a bit for Typescript with Tslint, but you'll get the idea)

export interface Props {    // tslint:disable-next-line:no-any    records: Array<any>;}// "dumb" Component that converts props into presentation class MyComponent extends React.Component<Props> {    // tslint:disable-next-line:no-any    constructor(props: Props) {        super(props);    }    render() {        return (            <div className="async_component">                {this._renderList()}            </div>        );    }    _renderList() {        // tslint:disable-next-line:no-any        return this.props.records.map((record: any) => {            return (                <div className="record" key={record.name}>                    <p>{record.name}</p>                    <p>{record.utility}</p>                </div>            );        });    }}// Container class with the async data loadingclass MyAsyncContainer extends React.Component<{}, Props> {    constructor(props: Props) {        super(props);        this.state = {            records: []        };    }    componentDidMount() {        fetch('/some/url/that/returns/my/data')        .then((response) => response.json())        .then((data) => {            this.setState({                records: data.records            });        });    }    // render the "dumb" component and set its props    render() {        return (<MyComponent records={this.state.records}/>);    }}

Now you can test MyComponent rendering by giving your mock data as props.


Ignoring the, sane, advice to think again about the structure, one way to go about this could be:

  • Mock the request (fx with sinon), to make it return a promise for some records
  • use Enzyme's mount function
  • Assert that the state to not have your records yet
  • Have your rest function use done callback
  • Wait a bit (fx with setImmediate), this will make sure your promise is resolved
  • Assert on the mounted component again, this time checking that the state was set
  • Call your done callback to notify that the test has completed

So, in short:

// asyncComponentTests.jsdescribe("Async Component Tests", () => {    it("should render correctly after setState in componentDidMount executes", (done) => {        nock("http://some.url.com")           .get("/some/url/that/returns/my/data")           .reply(200, {               data: [                   { id: 1, name: "willson", utility: 88 },                   { id: 2, name: "jeffrey", utility: 102 }               ]           });        const wrapper = mount(<AsyncComponent />);        // make sure state isn't there yet        expect(wrapper.state).to.deep.equal({});        // wait one tick for the promise to resolve        setImmediate(() => {            expect(wrapper.state).do.deep.equal({ .. the expected state });            done();        });    });});

Note:

I have no clue about nock, so here I assume your code is correct


IMO, this is actually a common issue which appears more complicated because of promises and componentDidMount:You're trying to test a functions which are only defined within the scope of another function. i.e. You should split your functions out and test them individually.

Component

class AsyncComponent extends React.Component {    constructor(props) {        super(props);        this.state = {            records: []        };    }    componentDidMount() {        request.get('/some/url/that/returns/my/data')            .then(this._populateState);    }    render() {        return (            <div className="async_component">                { this._renderList() }            </div>        );    }    _populateState(data) {        this.setState({            records: data.records        });    }    _renderList() {        return this.state.records.map((record) => {            return (                <div className="record">                    <p>{ record.name }</p>                    <p>{ record.utility }</p>                </div>            );        });    }}

Unit Test

// asyncComponentTests.jsdescribe("Async Component Tests", () => {    describe("componentDidMount()", () => {        it("should GET the user data on componentDidMount", () => {            const data = {                records: [                    { id: 1, name: "willson", utility: 88 },                    { id: 2, name: "jeffrey", utility: 102 }                ]            };            const requestStub = sinon.stub(request, 'get').resolves(data);            sinon.spy(AsyncComponent.prototype, "_populateState");            mount(<AsyncComponent />);            assert(requestStub.calledOnce);            assert(AsyncComponent.prototype._populateState.calledWith(data));        });    });    describe("_populateState()", () => {        it("should populate the state with user data returned from the GET", () => {            const data = [                { id: 1, name: "willson", utility: 88 },                { id: 2, name: "jeffrey", utility: 102 }            ];            const wrapper = shallow(<AsyncComponent />);            wrapper._populateState(data);            expect(wrapper.state).to.deep.equal(data);        });    });});

Note: I've written the unit tests from documentation alone, so the use of shallow, mount, assert, and expect might not be best practices.