单元测试React单击组件外部

时间:2017-07-12 01:12:15

标签: javascript unit-testing reactjs jestjs enzyme

使用this answer中的代码解决组件外部的点击问题:

componentDidMount() {
    document.addEventListener('mousedown', this.handleClickOutside);
}

componentWillUnmount() {
    document.removeEventListener('mousedown', this.handleClickOutside);
}

setWrapperRef(node) {
    this.wrapperRef = node;
}

handleClickOutside(event) {
    if (this.wrapperRef && !this.wrapperRef.contains(event.target)) {
        this.props.actions.something() // Eg. closes modal
    }
}

我无法弄清楚如何对不愉快的路径进行单元测试,以便警报不会运行,到目前为止我所得到的:

it('Handles click outside of component', () => {
  props = {
    actions: {
      something: jest.fn(),
    }
  }
  const wrapper = mount(
    <Component {... props} />,
  )
  expect(props.actions.something.mock.calls.length).toBe(0)

  // Happy path should trigger mock

  wrapper.instance().handleClick({
    target: 'outside',
  })

  expect(props.actions.something.mock.calls.length).toBe(1)  //true

  // Unhappy path should not trigger mock here ???

  expect(props.actions.something.mock.calls.length).toBe(1)
})

我试过了:

  • 通过wrapper.html()
  • 发送
  • .find一个节点并通过发送(不会模拟event.target
  • .simulate click内部元素(不触发事件监听器)

我确定我错过了一些小事,但我无法在任何地方找到这样的例子。

5 个答案:

答案 0 :(得分:21)

import { mount } from 'enzyme'
import React from 'react'
import ReactDOM from 'react-dom'

it('Should not call action on click inside the component', () => {
  const map = {}

  document.addEventListener = jest.fn((event, cb) => {
    map[event] = cb
  })

  const props = {
    actions: {
      something: jest.fn(),
    }
  }

  const wrapper = mount(<Component {... props} />)

  map.mousedown({
    target: ReactDOM.findDOMNode(wrapper.instance()),
  })

  expect(props.actions.something).not.toHaveBeenCalled()
})

来自this酶问题的解决方案在github上。

答案 1 :(得分:2)

所选答案未涵盖handleClickOutside的else路径

我在ref元素上添加了mousedown事件,以触发handleClickOutside的else路径

import { mount } from 'enzyme'
import React from 'react'
import ReactDOM from 'react-dom'

it('Should not call action on click inside the component', () => {
  const map = {}

  document.addEventListener = jest.fn((event, cb) => {
    map[event] = cb
  })

  const props = {
    actions: {
      something: jest.fn(),
    }
  }
  //test if path of handleClickOutside
  const wrapper = mount(<Component {... props} />)

  map.mousedown({
    target: ReactDOM.findDOMNode(wrapper.instance()),
  })

  //test else path of handleClickOutside
  const refWrapper = mount(<RefComponent />)

  map.mousedown({
    target: ReactDOM.findDOMNode(refWrapper.instance()),
  })

  expect(props.actions.something).not.toHaveBeenCalled()
})

答案 2 :(得分:0)

我发现可以避免使用ReactDOM.findDOMNode的情况/解决方案。请看下面的例子:

import React from 'react';
import { shallow } from 'enzyme';

const initFireEvent = () => {
  const map = {};

  document.addEventListener = jest.fn((event, cb) => {
    map[event] = cb;
  });

  document.removeEventListener = jest.fn(event => {
    delete map[event];
  });

  return map;
};

describe('<ClickOutside />', () => {
  const fireEvent = initFireEvent();
  const children = <button type="button">Content</button>;

  it('should call actions.something() when clicking outside', () => {
    const props = {
      actions: {
       something: jest.fn(),
     }
    };

    const onClick = jest.fn();

    mount(<ClickOutside {...props}>{children}</ClickOutside>);
    fireEvent.mousedown({ target: document.body });

    expect(props.actions.something).toHaveBeenCalledTimes(1);
  });

  it('should NOT call actions.something() when clicking inside', () => {
    const props = {
      actions: {
       something: jest.fn(),
     }
    };

    const wrapper = mount(
      <ClickOutside onClick={onClick}>{children}</ClickOutside>,
    );

    fireEvent.mousedown({
      target: wrapper.find('button').instance(),
    });

    expect(props.actions.something).not.toHaveBeenCalled();
  });
});

版本:

"react": "^16.8.6",
"jest": "^25.1.0",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.2"

答案 3 :(得分:0)

最简单的事情就是在主体上调度事件

  mount(<MultiTagSelect {...props} />);
window.document.body.dispatchEvent(new Event('click'));

答案 4 :(得分:-1)

使用sinon跟踪是否调用handleClickOutside。顺便说一下,我刚刚发布了我需要在Nav组件中进行单元测试的项目。实际上,当您单击外部时,应关闭所有子菜单。

import sinon from 'sinon';
import Component from '../src/Component';

it('handle clicking outside', () => {
     const handleClickOutside = sinon.spy(Component.prototype, 'handleClickOutside');
     const wrapper = mount(
         <div> 
           <Component {... props} />
           <div><a class="any-element-outside">Anylink</a></div>
         </div>
      ); 

      wrapper.find('.any-element-outside').last().simulate('click'); 
      expect(handleClickOutside.called).toBeTruthy(); 
      handleClickOutside.restore(); 
})