【问题标题】:How Do I get an HOC wrapped component function from enzyme instance when mounting?安装时如何从酶实例中获取 HOC 包装的组件功能?
【发布时间】:2018-12-13 21:34:25
【问题描述】:

我有父组件 -> 子组件,子组件在 componentDidMount 内运行 fetch。 fetch 在父级之上的 redux 存储中设置状态,这会导致子组件显示不同。

孩子也在使用material-uiwithStyles,所以它围绕组件创建了一个HOC。

在我的测试中,我需要挂载父组件,找到子组件,然后看到fetch已经正确改变状态并导致子更新。

到目前为止我的解决方案是这样的:

  • 装载父母,寻找孩子
  • 致电child.instance().fetchFunction().then(() => expect(..))

但是,在 child 上调用 instance() 会返回 HOC,因此出现错误:

child.instance(...).fetchFunction 不是函数

我见过的所有解决方案都使用 shallowdive 来绕过 HOC,但是如果我使用 shallow 我将不得不在测试中创建一个模拟存储,它实际上不会测试这个集成测试。

我可以测试单独的 fetch 调用,然后使用 shallow 测试组件并将 props 传递给它,就好像状态已更改一样,但这并不能证明它们可以一起工作。

这是一个代码框,我在其中重现了该问题:

这是一些示例代码(基本上是代码框):

App.js

import React from "react";
import Child from "./Child";

class App extends React.Component {
  render() {
    return <Child />;
  }
}

export default App;

Child.js

import React from "react";
import { withStyles } from "@material-ui/core/styles";

const childStyles = {
  margin: 0
};

class Child extends React.Component {
  state = {
    groceries: [],
    errorStatus: ""
  };

  componentDidMount() {
    console.log("calling fetch");

    this.fetchCall();
  }

  fetchCall = () => {
    return fetch("/api/v1/groceries")
      .then(this.checkStatus)
      .then(this.parseJSON)
      .then(this.setStateFromData)
      .catch(this.setError);
  };

  checkStatus = results => {
    if (results.status >= 400) {
      console.log("bad status");

      throw new Error("Bad Status");
    }

    return results;
  };

  setError = () => {
    console.log("error thrown");

    return this.setState({ errorStatus: "Error fetching groceries" });
  };

  parseJSON = results => {
    console.log("parse json");

    return results.json();
  };

  setStateFromData = data => {
    console.log("setting state");

    return this.setState({ groceries: data.groceries });
  };

  render() {
    const { groceries } = this.state;

    return (
      <div id="app">
        {groceries.map(grocery => {
          return <div key={grocery.id}>{grocery.item}</div>;
        })}
      </div>
    );
  }
}

export default withStyles(childStyles)(Child);

App.test.js

import Enzyme from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import React from "react";
import { mount } from "enzyme";
import App from "./App";
import Child from "./Child";

Enzyme.configure({ adapter: new Adapter() });

const mockResponse = (status, statusText, response) => {
  return new window.Response(response, {
    status: status,
    statusText: statusText,
    headers: {
      "Content-type": "application/json"
    }
  });
};

describe("App", () => {
  describe("ChildApp componentDidMount", () => {
    it("sets the state componentDidMount", () => {
      console.log("starting test for 200");

      global.fetch = jest.fn().mockImplementation(() =>
        Promise.resolve(
          mockResponse(
            200,
            null,
            JSON.stringify({
              groceries: [{ item: "nuts", id: 10 }, { item: "greens", id: 3 }]
            })
          )
        )
      );

      const renderedComponent = mount(<App />);
      const childApp = renderedComponent.find(Child);

      childApp
        .instance()
        .fetchCall()
        .then(() => {
          console.log("finished test for 200");
          expect(childApp.state("groceries").length).toEqual(2);
        });
    });

    it("sets the state componentDidMount on error", () => {
      console.log("starting test for 500");

      window.fetch = jest
        .fn()
        .mockImplementation(() =>
          Promise.resolve(
            mockResponse(
              400,
              "Test Error",
              JSON.stringify({ status: 400, statusText: "Test Error!" })
            )
          )
        );

      const renderedComponent = mount(<App />);
      const childApp = renderedComponent.find(Child);
      childApp
        .instance()
        .fetchCall()
        .then(() => {
          console.log("finished test for 500");
          expect(childApp.state("errorStatus")).toEqual(
            "Error fetching groceries"
          );
        });
    });
  });
});

【问题讨论】:

  • 测试方法有问题。 它实际上不会像我想要的那样端到端测试这个……调用 child.instance().fetchFunction().then(() => expect(..)) -这不是 e2e 测试,而是某种低效的集成测试。您仍在直接测试子单元,但这样做的方式几乎没有道理。作为 App 测试的一部分,没有什么要求对 Child 进行测试。无论如何,您都可以通过 innerRef, material-ui.com/customization/css-in-js/… 获得对 withStyles HOC 内部组件的引用
  • 你说得对,不是e2e,我已经改成“集成测试”了。我试过使用instance().innerRef,但它返回undefined

标签: javascript reactjs jestjs enzyme


【解决方案1】:

写完之后,我找到了答案,但我觉得这个值得分享,因为我很困惑。

不要使用app.find(Child)(组件构造函数),而是使用app.find('Child')(组件显示名称)。这将找到实际的组件,而不是 hoc 包装的组件。

enzyme docs for find(selector)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-22
    • 1970-01-01
    • 2017-12-24
    • 2020-10-29
    • 2023-03-04
    • 1970-01-01
    相关资源
    最近更新 更多