Testing asynchronous componentDidMount that changes state with Jest and Enzyme

元气小坏坏 提交于 2021-01-28 02:05:35

问题


All I am doing in my code is upon componentDidMount being run, I am making an axios get request to GitHub, and setting some data back onto state. However, when I run the test it still says the state is an empty array.

Here is my component below:

export default class HelloWorld extends Component {
    constructor(props) {
        super(props)
        this.state = {
            goodbye: false,
            data: []
        }

    }

    async componentDidMount() {
        await this.func()
    }

    func = async () => {
        let data = await axios.get('https://api.github.com/gists')
        this.setState({data: data.data})
        console.log(this.state)
    }

    goodbye = () => {
        this.setState((state, currentProps) => ({...state, goodbye: !state.goodbye}))
    }

    render() {
        return (
            <Fragment>
                <h1>
                    Hello World
                </h1>
                <button id="test-button" onClick={this.goodbye}>Say Goodbye</button>
                {
                    !this.state.goodbye ? null :
                    <h1 className="goodbye">GOODBYE WORLD</h1>
                }
            </Fragment>
        )
    }
}

and here is my test:

it('there is data being returned', async () => { 
    const component =  await mount(<HelloWorld />)       

    component.update()

    expect(component.state('data')).toHaveLength(30)

})

I am very new to using Jest and am not sure what I am doing wrong. This app was built solely for testing out Jest with Enzyme. How can I test this component correctly?


回答1:


First of all you need to mock await axios.get('https://api.github.com/gists') somehow.

Them probably you'll need to do something like this after component.update() to "wait".




回答2:


Testing a component that runs an async function from componentDidMount must await a re-render before running assertions. wrapper.update is used to sync Enzyme's component tree before running assertions but doesn't wait for promises or force a re-render.

One approach is to use setImmediate, which runs its callback at the end of the event loop, allowing promises to resolve non-invasively.

Here's a minimal example for your component:

import React from "react";
import Enzyme, {mount} from "enzyme";
import Adapter from "enzyme-adapter-react-16";
Enzyme.configure({adapter: new Adapter()});
import mockAxios from "axios";
import HelloWorld from "./HelloWorld";

jest.mock("axios");

describe("HelloWorld", () => {
  beforeEach(() => jest.resetAllMocks());

  it("should call `axios.get` and set the response to `state.data`", async () => {
    const mockData = ["foo", "bar", "baz"];
    mockAxios.get.mockImplementationOnce(() => Promise.resolve({data: mockData}));

    const wrapper = mount(<HelloWorld />);
    await new Promise(setImmediate);
    wrapper.update();

    expect(mockAxios.get).toHaveBeenCalledTimes(1);
    expect(wrapper.instance().state.data).toEqual(mockData);
  });
});

While this works, wrapper.instance().state.data is too tied to the internal state of the application. I'd prefer to write a test that asserts on the behavior, not the implementation; variable name changes should not force tests to be re-written.

Here's an example component where the updated data is rendered in the DOM and tested in a more black-box manner:

Component (StackUsers.js):

import axios from "axios";
import React from "react";

export default class StackUsers extends React.Component {
  constructor(props) {
    super(props);
    this.state = {users: null};
  }

  componentDidMount() {
    this.getUsers();
  }

  async getUsers() { 
    const ids = this.props.ids.join(";");
    const url = `https://api.stackexchange.com/2.2/users/${ids}?site=stackoverflow`;
    const res = await axios.get(url);
    this.setState({users: res.data.items});
  }

  render() {
    const {users} = this.state;
    return (
      <>
        {users
          ? <ul data-test="test-stack-users-list">{users.map((e, i) => 
              <li key={i}>{e.display_name}</li>
            )}</ul>
          : <div>loading...</div>
        }
      </>
    );
  }
}

Test (StackUsers.test.js):

import React from "react";
import Enzyme, {mount} from "enzyme";
import Adapter from "enzyme-adapter-react-16";
Enzyme.configure({adapter: new Adapter()});
import mockAxios from "axios";
import StackUsers from "../src/StackUsers";

jest.mock("axios");

describe("StackUsers", () => {
  beforeEach(() => jest.resetAllMocks());

  it("should load users", async () => {
    mockAxios.get.mockImplementationOnce(() => Promise.resolve({
      data: {
        items: [
          {"display_name": "Jeff Atwood"},
          {"display_name": "Joel Spolsky"},
        ]
      },
      status: 200
    }));

    const wrapper = mount(<StackUsers ids={[1, 4]} />);
    let users = wrapper.find('[data-test="test-stack-users-list"]');
    expect(users.exists()).toBe(false);

    await new Promise(setImmediate);
    wrapper.update();

    expect(mockAxios.get).toHaveBeenCalledTimes(1);
    users = wrapper.find('[data-test="test-stack-users-list"]');
    expect(users.exists()).toBe(true);
    expect(users.children()).toHaveLength(2);
    expect(users.children().at(0).text()).toEqual("Jeff Atwood");
    expect(users.children().at(1).text()).toEqual("Joel Spolsky");
  });
});

Now, the only things the test knows is that axios.get is used and the results should appear in an element with a specific data-test attribute. This does not unduly interfere with CSS classes, HTML structure or component internals.

For reference, here are my dependencies:

}
  "dependencies": {
    "axios": "^0.18.0",
    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "react-redux": "^7.0.3",
    "redux": "^4.0.1"
  },
  "devDependencies": {
    "enzyme": "^3.9.0",
    "enzyme-adapter-react-16": "^1.12.1",
    "react-scripts": "^1.0.11",
    "react-test-renderer": "^16.8.6"
  }
}


来源:https://stackoverflow.com/questions/54465734/testing-asynchronous-componentdidmount-that-changes-state-with-jest-and-enzyme

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!