javascript - How to TEST async calls made in componentDidMount that set the state of React Component - Stack Overflow

What is the best way to test that an async call within ponentDidMount sets the state for a React ponent

What is the best way to test that an async call within ponentDidMount sets the state for a React ponent? For context, the libraries I'm using for testing are Mocha, Chai, Enzyme, and Sinon.

Here's an example code:

/* 
 * assume a record looks like this:
 * { id: number, name: string, utility: number }
 */

// asyncComponent.js
class AsyncComponent extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            records: []
        };
    }

    ponentDidMount() {
        // assume that I'm using a library like `superagent` to make ajax calls that returns Promises

        request.get('/some/url/that/returns/my/data').then((data) => {
            this.setState({
                records: data.records
            });
        });
    }

    render() {
        return (
            <div className="async_ponent">
                { this._renderList() }
            </div>
        );
    }

    _renderList() {
        return this.state.records.map((record) => {
            return (
                <div className="record">
                    <p>{ record.name }</p>
                    <p>{ record.utility }</p>
                </div>
            );
        });
    }
}


// asyncComponentTests.js
describe("Async Component Tests", () => {
    it("should render correctly after setState in ponentDidMount executes", () => {
        // I'm thinking of using a library like `nock` to mock the http request

       nock("")
           .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 />);

       // NOW WHAT? This is where I'm stuck.
    });
});

What is the best way to test that an async call within ponentDidMount sets the state for a React ponent? For context, the libraries I'm using for testing are Mocha, Chai, Enzyme, and Sinon.

Here's an example code:

/* 
 * assume a record looks like this:
 * { id: number, name: string, utility: number }
 */

// asyncComponent.js
class AsyncComponent extends React.Component {
    constructor(props) {
        super(props);

        this.state = {
            records: []
        };
    }

    ponentDidMount() {
        // assume that I'm using a library like `superagent` to make ajax calls that returns Promises

        request.get('/some/url/that/returns/my/data').then((data) => {
            this.setState({
                records: data.records
            });
        });
    }

    render() {
        return (
            <div className="async_ponent">
                { this._renderList() }
            </div>
        );
    }

    _renderList() {
        return this.state.records.map((record) => {
            return (
                <div className="record">
                    <p>{ record.name }</p>
                    <p>{ record.utility }</p>
                </div>
            );
        });
    }
}


// asyncComponentTests.js
describe("Async Component Tests", () => {
    it("should render correctly after setState in ponentDidMount executes", () => {
        // I'm thinking of using a library like `nock` to mock the http request

       nock("http://some.url.")
           .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 />);

       // NOW WHAT? This is where I'm stuck.
    });
});
Share Improve this question edited Aug 3, 2016 at 22:07 wmock asked Aug 3, 2016 at 20:20 wmockwmock 5,5025 gold badges43 silver badges62 bronze badges 7
  • Wouldn't you just assert that your state updated correctly? I'm not all that familiar with using Enzyme and not using the shallow() api, but with shallow-rendered ponents you can assume that the state update is synchronous. – Michael Parker Commented Aug 3, 2016 at 21:54
  • My question is more focused on the async part of this - if I were to assert the state initially after render, records would be the empty array. Instead, I'm hoping to make the assertion after the promise in ponentDidMount sets the state to a non-empty array. – wmock Commented Aug 3, 2016 at 22:05
  • 2 In reality, it is best practice to move that functionality out of the ponent so it can be tested separately and you can mock it for testing the ponent. But you could always use setTimeout. You have control over nock so you can be pretty sure about how long the response will take. – aray12 Commented Aug 4, 2016 at 0:34
  • Just so I understand, when you're using nock, you can control how long it takes before the response es back? And assuming thats the case, then you can make an assertion within the setTimeout after that amount of time has elapsed? – wmock Commented Aug 4, 2016 at 5:27
  • 1 You can and it should work, but the suggestion to use a Flux architecture (i.e. redux) is also a good one. Your ponent's will be "dumb" in the sense that they just render what you give them, making testing easier (and synchronous). – ivarni Commented Aug 4, 2016 at 14:43
 |  Show 2 more ments

3 Answers 3

Reset to default 3

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 ponent 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_ponent">
                {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 loading
class MyAsyncContainer extends React.Component<{}, Props> {

    constructor(props: Props) {
        super(props);

        this.state = {
            records: []
        };
    }

    ponentDidMount() {

        fetch('/some/url/that/returns/my/data')
        .then((response) => response.json())
        .then((data) => {
            this.setState({
                records: data.records
            });
        });
    }

    // render the "dumb" ponent 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 ponent again, this time checking that the state was set
  • Call your done callback to notify that the test has pleted

So, in short:

// asyncComponentTests.js
describe("Async Component Tests", () => {
    it("should render correctly after setState in ponentDidMount executes", (done) => {
        nock("http://some.url.")
           .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 mon issue which appears more plicated because of promises and ponentDidMount: 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: []
        };
    }

    ponentDidMount() {
        request.get('/some/url/that/returns/my/data')
            .then(this._populateState);
    }

    render() {
        return (
            <div className="async_ponent">
                { 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.js
describe("Async Component Tests", () => {
    describe("ponentDidMount()", () => {
        it("should GET the user data on ponentDidMount", () => {
            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.

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744218899a4563684.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信