【发布时间】: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 不是函数
我见过的所有解决方案都使用 shallow 和 dive 来绕过 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