54

Hy, I don't know how to mock an inline function in React component's child

My stack: sinon, chai, enzyme;

Component usage:

<ListItem onClick={() => someFn()} />

Component's render:

render() {
    return (
      <li>
        <a href="#" onClick={e => {
            e.preventDefault();
            this.props.onClick();
          }}
        > whatever </a>
      </li>
    );
  }

Here we have onClick function that calls e.preventDefault(). How to tell to <a href>(link) to not to call e.preventDefault()? How can I mock an onClick?

Below is what I have tried in tests:

Shallow copy setup

function setup() {
  const someFn = sinon.stub();

  const component = shallow(
    <ListItem
      onClick={() => {
        someFn();
      }}
    />
  );

  return {
    component: component,
    actions: someFn,
    link: component.find('a'),
    listItem: component.find('li'),
  }
}

And the test

  it('simulates click events', () => {
    const { link, actions } = setup();
    link.simulate('click'); //Click on <a href>
    expect(actions).to.have.property('callCount', 1); //will be fine if we remove e.preventDefault()
  });

Test's output error:

TypeError: Cannot read property 'preventDefault' of undefined
S Panfilov
  • 16,641
  • 17
  • 74
  • 96

7 Answers7

108

Try this

link.simulate('click', {
  preventDefault: () => {
  }
 });
WitVault
  • 23,445
  • 19
  • 103
  • 133
10
 test('simulates click events', () => {
    const e = { stopPropagation: jest.fn() };
    const component = shallow(<ListItem{...props} />);
    const li = component.find('li').at(0).childAt(0)
    li.props().onClick(e)

    expect();
  });
Anton Karpenko
  • 109
  • 1
  • 4
8

For those using Jest and @testing-library or react-testing-librarys fireEvent, you need to provide an initialised event object, otherwise the event can't be dispatched via your element.

One can then assert on e.preventDefault being called by assigning a property to that initialised event:

test('prevents default on click', () => {
  const {getByText} = render(<MyComponent />);
  const button = getByText(/click me/);

  // initialise an event, and assign your own preventDefault
  const clickEvent = new MouseEvent('click');
  Object.assign(clickEvent, {preventDefault: jest.fn()});

  fireEvent(button, clickEvent);

  expect(clickEvent.preventDefault).toHaveBeenCalledTimes(1);
});

Similarly for stopPropagation.

Anton Karpenko's answer for Jest was useful.

Larry
  • 1,238
  • 2
  • 20
  • 25
6

Just to note that this is an issue only when using shallow enzyme renderer. In case of full DOM renderer mount, the event object contains the preventDefault method, therefore you don't have to mock it.

Matus Dubrava
  • 13,637
  • 2
  • 38
  • 54
4

You can define an object with regarding function you will mock via some testing tool, for example look at Jest and Enzyme

describe('Form component', () => {
  test('deos not reload page after submition', () => {
    const wrapper = shallow(<TodosForm />)
    // an object with some function
    const event = { preventDefault: () => {} }
    // mocks for this function
    jest.spyOn(event, 'preventDefault')
    wrapper.find('form').simulate('submit', event)
    // how would you know that function is called
    expect(event.preventDefault).toBeCalled()
  })
})
Purkhalo Alex
  • 3,309
  • 29
  • 27
1

I would suggest to create new object based on jest.fn() with

const event = Object.assign(jest.fn(), {preventDefault: () => {}})

then use it:

element.simulate('click', event);
VonAxt
  • 95
  • 9
1

I am using Web Components and this works for me -

  const callback = jest.fn();
  MouseEvent.prototype.stopPropagation = callback;
  const element = createElement({});
  element.shadowRoot.querySelector('ul').click();
  expect(callback).toHaveBeenCalledTimes(1);
Ishank
  • 2,860
  • 32
  • 43