使用酶测试点击监听器

时间:2019-06-06 21:21:18

标签: javascript reactjs jestjs enzyme

我有一个有状态组件,该组件在安装时附加了一个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

为什么我的点击模拟无法正确更新组件状态?谢谢!

1 个答案:

答案 0 :(得分:2)

根据设计,由于酶是Javascript implementation and not a React implementation,因此酶不支持事件侦听器。因此,您必须做一些Javascript和开玩笑的技巧才能模仿事件侦听器。

在这种情况下,您实际上不需要测试事件处理程序,因为您只是在操纵状态。绕过事件侦听器,您可以手动操作onClick类属性,并声明状态和DOM如何相应更改-这将是一个更以React为中心的测试。但是,由于onClick需要一个真正的DOM节点,因此即使这样做也有些困难。因此,一种更简单的方法是直接使用wrapper.setState({ ... })操作状态并针对DOM更改进行断言。

在旁注中,我更喜欢使用className而不是data-attributes,因为它们在样式测试中更有用,而它们却没有不会用很多不必要的和/或未使用的属性来污染DOM。

下面的示例涵盖了所有3个选项。

  • ClickHandlerEvent.test.js (模拟事件)
  • ClickHandlerHandleClick.test.js (模拟handleClick)
  • ClickHandler.test.js (操作状态)

工作示例(单击Tests右侧的Browser标签-运行所有测试):

Edit Click Listener Testing


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;

选项1

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

选项2

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

选项3

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