我无法测试会通过玩笑和酶改变状态的异步componentDidMount

时间:2019-01-31 17:03:43

标签: javascript reactjs testing jestjs enzyme

我在代码中所做的只是在运行componentDidMount时,我向Github发出axios get请求,并将一些数据设置回状态,但是当我运行测试时,它仍然说状态是一个空数组,这是我下面的组件:

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>
    )
}

}

这是我的测试:

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

    component.update()

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

})

我对使用笑话非常陌生,不知道自己在做什么错,此应用程序仅用于测试酶的笑话。请让我知道我做错了什么!

2 个答案:

答案 0 :(得分:0)

首先,您需要以某种方式模拟等待axios.get('https://api.github.com/gists')。

您可能需要在component.update()之后执行this之类的操作才能“等待”。

答案 1 :(得分:0)

要测试从componentDidMount运行异步功能的组件,必须先等待重新渲染,然后再运行断言。 wrapper.update用于在运行断言之前同步酶的组件树,但不等待承诺或强制重新渲染。

一种方法是使用setImmediate,该方法在事件循环结束时运行其回调,从而允许承诺以非侵入方式解决。

这是您的组件的最小示例:

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);
  });
});

尽管这可行,但是wrapper.instance().state.data与应用程序的内部状态紧密相关。我更喜欢编写一个对行为进行断言的测试,而不是对实现的断言;变量名称的更改不应强制重写测试。

这是一个示例组件,其中更新的数据在DOM中呈现并以更黑盒的方式进行测试:

组件(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>
        }
      </>
    );
  }
}

测试(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");
  });
});

现在,测试唯一知道的是使用了axios.get,结果应该出现在具有特定data-test属性的元素中。这不会过度干扰CSS类,HTML结构或组件内部。

作为参考,这是我的依赖项:

}
  "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"
  }
}