【问题标题】:Testing asynchronous componentDidMount that changes state with Jest and Enzyme测试用 Jest 和 Enzyme 改变状态的异步 componentDidMount
【发布时间】:2019-06-25 05:14:11
【问题描述】:

我在代码中所做的一切都是在运行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)

})

我对使用 Jest 很陌生,不确定自己做错了什么。这个应用程序专门用于测试 Jest with Enzyme。如何正确测试这个组件?

【问题讨论】:

    标签: javascript reactjs testing jestjs enzyme


    【解决方案1】:

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

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

    【讨论】:

    • 如果我只想检查状态是否发生变化,为什么还要模拟 axios.get()?
    • @mlisonek,您对该州有何期待?为什么你认为数据应该有长度(30)?为什么不是 50。我在你的测试中没有看到。
    • 我可以看到从前端返回的数据及其包含有关要点信息的 30 个对象的数组
    • @mlisonek,你的意思是它是来自真实数据吗?来自网络?
    【解决方案2】:

    测试从componentDidMount(或useEffect——参见this answer)运行异步函数的组件必须在运行断言之前等待重新渲染。 wrapper.update 用于在运行断言之前同步 Enzyme 的组件树,但不等待承诺或强制重新渲染。

    一种方法是使用setImmediate,它在事件循环结束时运行它的回调,允许以非侵入方式解决promise。

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

    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/components/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"]')
          .hostNodes()
        ;
        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"]')
          .hostNodes()
        ;
        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"
      },
      "devDependencies": {
        "enzyme": "3.9.0",
        "enzyme-adapter-react-16": "1.12.1",
        "jest": "24.7.1",
        "jest-environment-jsdom": "24.7.1"
      }
    }
    

    【讨论】:

      猜你喜欢
      • 2018-12-23
      • 2018-08-31
      • 2016-11-13
      • 2020-03-15
      • 2023-03-27
      • 2022-01-18
      • 2020-01-20
      • 2019-03-18
      • 1970-01-01
      相关资源
      最近更新 更多