我有一个有状态组件,该组件在安装时附加了一个dom事件侦听器。如果用户单击给定元素,则另一个给定元素将有条件地出现和消失。我想为此编写一个测试,但是当我使用酶这样做时,出现错误:
sampleComponent.js:
import React from 'react';
class SampleComponent extends React.Component {
constructor() {
super();
this.state = {
onClick: false,
};
this.handleClick = this.handleClick.bind(this);
}
componentDidMount() {
document.addEventListener('mousedown', this.handleClick);
}
componentWillUnmount() {
document.removeEventListener('mousedown', this.handleClick);
}
handleClick(event) {
if (this.divRef && this.divRef.contains(event.target)) {
this.setState(prevState => ({ onClick: !prevState.onClick }));
}
}
render() {
return (
<div
ref={(node) => { this.divRef = node; }}
test-attr="div"
>
{
this.state.onClick && <p test-attr="p">clicked!</p>
}
</div>
);
}
}
export default SampleComponent;
sampleComponent.test.js:
import React from 'react';
import { shallow } from 'enzyme';
import SampleComponent from './sampleComponent';
test('renders component without errors', () => {
const wrapper = shallow(<SampleComponent />);
const div = wrapper.find('[test-attr="div"]');
const p = wrapper.find('[test-attr="p"]');
div.simulate('click');
expect(p.length).toEqual(1);
});
错误:
Error:
expect(received).toEqual(expected)
Expected value to equal:
1
Received:
0
Expected :1
Actual :0
为什么我的点击模拟无法正确更新组件状态?谢谢!
答案 0 :(得分:2)
根据设计,由于酶是Javascript implementation and not a React implementation,因此酶不支持事件侦听器。因此,您必须做一些Javascript和开玩笑的技巧才能模仿事件侦听器。
在这种情况下,您实际上不需要测试事件处理程序,因为您只是在操纵状态。绕过事件侦听器,您可以手动操作onClick
类属性,并声明状态和DOM如何相应更改-这将是一个更以React为中心的测试。但是,由于onClick
需要一个真正的DOM节点,因此即使这样做也有些困难。因此,一种更简单的方法是直接使用wrapper.setState({ ... })
操作状态并针对DOM更改进行断言。
在旁注中,我更喜欢使用className
而不是data-attributes
,因为它们在样式和测试中更有用,而它们却没有不会用很多不必要的和/或未使用的属性来污染DOM。
下面的示例涵盖了所有3个选项。
工作示例(单击Tests
右侧的Browser
标签-运行所有测试):
components / ClickHandler / index.js (组件)
import React, { Fragment, Component } from "react";
import ClickBox from "../ClickBox";
class ClickHandler extends Component {
state = {
isVisible: false
};
componentDidMount = () => {
document.addEventListener("mousedown", this.handleClick);
};
componentWillUnmount = () => {
document.removeEventListener("mousedown", this.handleClick);
};
handleClick = ({ target }) => {
this.setState({
isVisible: this.wrapperRef && this.wrapperRef.contains(target)
});
};
render = () => {
const { isVisible } = this.state;
return (
<div className="wrapper" ref={node => (this.wrapperRef = node)}>
<ClickBox>
<p className="instruction">
(click <strong>{isVisible ? "outside" : "inside"}</strong> the box
to <strong>{isVisible ? "hide" : "show"}</strong> the message)
</p>
<h2 className="message">
{isVisible ? (
<Fragment>
Hello <strong>World</strong>!
</Fragment>
) : null}
</h2>
</ClickBox>
</div>
);
};
}
export default ClickHandler;
components / ClickHandler / __ tests __ / ClickHandlerEvent.test.js (模拟事件)
import React, { Fragment } from "react";
import { mount } from "enzyme";
import ClickHandler from "../index";
const initialState = {
isVisible: false
};
// elevating the event listener to the test
const eventListener = {};
document.addEventListener = (evt, cb) => (eventListener[evt] = cb);
describe("Click Handler", () => {
let wrapper;
beforeAll(() => {
wrapper = mount(
<Fragment>
<ClickHandler />
<div className="outside" />
</Fragment>
);
wrapper.setState({ ...initialState });
});
afterAll(() => {
wrapper.unmount();
});
it("renders without errors and the message should be hidden", () => {
expect(wrapper.find("div.wrapper")).toHaveLength(1);
expect(wrapper.find("h2.message").text()).toEqual("");
});
it("displays a message when a click is inside of the box", () => {
// manually triggering the event listener with a node
// inside of "ClickHandler"
eventListener.mousedown({
target: wrapper
.find("ClickHandler")
.getDOMNode()
.getElementsByClassName("instruction")[0]
});
expect(wrapper.find("ClickHandler").state("isVisible")).toBeTruthy();
expect(wrapper.find("h2.message").text()).toEqual("Hello World!");
});
it("hides the message when the click is outside of the box", () => {
// manually triggering the event listener with a node
// outside of "ClickHandler"
eventListener.mousedown({
target: wrapper.find("div.outside").getDOMNode()
});
expect(wrapper.find("ClickHandler").state("isVisible")).toBeFalsy();
expect(wrapper.find("h2.message").text()).toEqual("");
});
});
components / ClickHandler / __ tests __ / ClickHandlerHandleClick.test.js (模仿handleClick)
import React, { Fragment } from "react";
import { mount } from "enzyme";
import ClickHandler from "../index";
const initialState = {
isVisible: false
};
describe("Click Handler", () => {
let wrapper;
beforeAll(() => {
wrapper = mount(
<Fragment>
<ClickHandler />
<div className="outside" />
</Fragment>
);
wrapper.setState({ ...initialState });
});
afterAll(() => {
wrapper.unmount();
});
it("renders without errors and the message should be hidden", () => {
expect(wrapper.find("div.wrapper")).toHaveLength(1);
expect(wrapper.find("h2.message").text()).toEqual("");
});
it("displays a message when a click is inside of the box", () => {
// manually triggering the handleClick class property with a
// node inside of "ClickHandler"
wrapper
.find("ClickHandler")
.instance()
.handleClick({
target: wrapper
.find("ClickHandler")
.getDOMNode()
.getElementsByClassName("instruction")[0]
});
expect(wrapper.find("ClickHandler").state("isVisible")).toBeTruthy();
expect(wrapper.find("h2.message").text()).toEqual("Hello World!");
});
it("hides the message when the click is outside of the box", () => {
// manually triggering the handleClick class property with a
// node outside of "ClickHandler"
wrapper
.find("ClickHandler")
.instance()
.handleClick({
target: wrapper.find("div.outside").getDOMNode()
});
expect(wrapper.find("ClickHandler").state("isVisible")).toBeFalsy();
expect(wrapper.find("h2.message").text()).toEqual("");
});
});
components / ClickHandler / __ tests __ / ClickHandler.test.js (操作状态)
import React from "react";
import { mount } from "enzyme";
import ClickHandler from "../index";
const initialState = {
isVisible: false
};
describe("Click Handler", () => {
let wrapper;
beforeAll(() => {
wrapper = mount(<ClickHandler />);
wrapper.setState({ ...initialState });
});
afterAll(() => {
wrapper.unmount();
});
it("renders without errors and the message should be hidden", () => {
expect(wrapper.find("div.wrapper")).toHaveLength(1);
expect(wrapper.find("h2.message").text()).toEqual("");
});
it("displays a message when a click is inside of the box", () => {
// manually manipulating state
wrapper.setState({ isVisible: true });
expect(wrapper.find("h2.message").text()).toEqual("Hello World!");
});
it("hides the message when the click is outside of the box", () => {
// manually manipulating state
wrapper.setState({ isVisible: false });
expect(wrapper.find("h2.message").text()).toEqual("");
});
});