enzyme: react-select simulate 'change' not working

How i can simulate ‘change’ event on react-select comonent?

wrapper.find('Select').simulate('change', { target: { value: 'text' }}); don’t trigger change event

wrapper.props().onChange(event) working well

any ideas?

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Reactions: 10
  • Comments: 16 (1 by maintainers)

Most upvoted comments

@idoo so with shallow I was able to get a passing test using react-select. You can see it here. enzyme-test-repo/tree/issue-#400. It seems that your find query is not accurately querying the node that the change event should be simulated on. If you just use shallow and query for the <Select/> component you can easily test that the onChange prop is being called.

ReactSelectTestComponent.js

import React from 'react';
import Select from 'react-select';


const options = [
  { value: 'one', label: 'One' },
  { value: 'two', label: 'Two' },
]

export default class ReactSelectTestComponent extends React.Component {


  constructor(props) {
    super(props);
    this.onChange = this.onChange.bind(this);
  }

  onChange(event) {
    if (this.props.onChange) {
      this.props.onChange(event)
    }
  }

  render() {
    return (
      <div>
        <h1>Hello, world</h1>
        <Select
          name='test'
          value='one'
          options={options}
          onChange={this.onChange}
        />
      </div>
    )
  }
}

test.js

describe('react-select', () => {
  it('should call onChange with shallow', () => {
    const onChange = sinon.spy();
    const wrapper = shallow(<ReactSelectTestComponent onChange={onChange}/>);
    const selectWrapper = wrapper.find('Select');
    selectWrapper.simulate('change');
    expect(onChange.called).to.be.true;
  });
})

As for mount it seems likely that react-select isn’t actually registering a change event on the input your querying, I’d have to look at the source more but it doesn’t seem like they actually register an onChange handler with their DOM nodes.

If you look at the test files for react-select you can see they are using a specific node to test change events:

var typeSearchText = (text) => {
    TestUtils.Simulate.change(searchInputNode, { target: { value: text } });
};

Where searchInputNode seems to be defined as:


var searchInstance = ReactDOM.findDOMNode(instance.refs.input);
searchInputNode = null;
if (searchInstance) {
    searchInputNode = searchInstance.querySelector('input');
    if (searchInputNode) {
        TestUtils.Simulate.focus(searchInputNode);
    }

So you may want to follow that as an example of testing change events with mount, though shallow does test the onChange prop just fine.

EDIT: rework for the Select.Async

Hi all, I put it to work with the following code (I hope I don’t forget any portion of the code in the copy paste :-p ) :

import React from 'react';
import { mount } from 'enzyme';
import { expect } from 'chai';
import sinon from 'sinon';

import SearchableSelect from '../../../components/Form/SearchableSelect';

import Select from 'react-select';

describe('<SearchableSelect /> component', () => {
  const input = {
    value: undefined,
    onChange: sinon.spy(),
  };
  const options = [
    { label: 'Tic', value: 'Tac' },
    { label: 'Timon', value: 'Pumbaa' },
  ];
  const callback = sinon.spy();

  let wrapper;
  let select;

  beforeEach(() => {
    input.onChange.reset();
    callback.reset();
  });

  context('with a `static` fetcher', () => { // <Select />
    const props = { input, callback, options };

    before(() => {
      wrapper = mount(<SearchableSelect {...props} />);
      select = wrapper.find(Select);
    });

    it('should call input.onChange and the callback on Select changes', () => {
      const selectInput = select.find('input');
      selectInput.simulate('change', { target: { value: options[0].value } });
      selectInput.simulate('keyDown', { keyCode: 9, key: 'Tab' });
      sinon.assert.callOrder(props.input.onChange, props.callback);
    });
  });

  context('with an async fetcher', () => {
    const promise = Promise.resolve({ options: [options[1]] });
    const loadOptions = sinon.stub().withArgs(options[1].value).returns(promise);

    const props = {
      callback,
      input,
      loadOptions,
    };

    before(() => {
      wrapper = mount(<SearchableSelect {...props} />);
      select = wrapper.find(Select); // Select is a child component of Async
    });

    it('should call input.onChange and the callback on Select changes', () => {
      // https://github.com/JedWatson/react-select/blob/8387e607666cca6dbfbf5860e53188b319cc43d5/test/Async-test.js#L33
      // https://github.com/JedWatson/react-select/blob/8387e607666cca6dbfbf5860e53188b319cc43d5/test/Async-test.js#L129
      select.props().onInputChange(options[1].value); // simulate a search and launch loadOptions
      const inp = select.find('input'); // find the hidden input to simulate an option selection
      inp.simulate('change', { target: { value: options[1].value } });
      inp.simulate('keyDown', { keyCode: 9, key: 'Tab' }); // validate the selection
      sinon.assert.callOrder(props.input.onChange, props.callback);
    });
  });
});

The key was to add an keyDown simulation to validate the change event

@jamcreencia simulate doesn’t actually simulate anything, which is why i recommend avoiding it. It’s just sugar for invoking a prop function. If you want to invoke onChange, use .prop(‘onChange’)().

I have had similar issues simulating changes with react-select, possibly due to using MaterialUI as well.

The solutions explained so far did not work for me unfortunately, but I finally came across a solution which may be useful to those using MaterialUI, react-select and enzyme:

const mockChangeCallBack = jest.fn();

const wrapper = mount(
      <ReactSelectTestComponent options={someOptions} onChange={mockChangeCallBack} />
    );

// Find input field on Select component (from the react-select module).
const input = wrapper.find(Select).find("input");

// Simulate the arrow down event to open the dropdown menu.
input.simulate("keyDown", { key: "ArrowDown", keyCode: 40 });

// Simulate the enter key to select the first option.
input.simulate("keyDown", { key: "Enter", keyCode: 13 });

// Assert that the onChange function has been called.
expect(mockOnChangeCB).toHaveBeenCalled();

/* 
 * Could further assert that the state has changed to the first 
 * option in the options list (e.g. someOptions[0]) etc...
 */

@Aweary

  it('call callback when value is changed', () => {
    let callback = sinon.spy();
    let props = extend({ onValueChanged: callback }, BASIC_PROPS);
    const wrapper = mount(createElement(SelectInput, props));
    wrapper.find('.Select-input input').simulate('change');
    expect(callback.called).to.be.true; //—> false ˘o˘
  });

Had a similar issue trying to use Select.Async …Thanks to Aweary’s post I could achieve that in Enzyme

       const wrapper = mount(< SomeComponentUsingSelectAsync onChange = {onChange} />);
        const select = wrapper.find(Select.Async);
        select.find('input').simulate('change', { target: {value:'some value'} }); ```

I created this utility function to Enzyme test react-select using Typescript. It returns a string reference to the component you can use to make assertions.

const selectOption = (
  wrapper: ReactWrapper<{}, {}, React.Component<{}, {}, any>>,
  selector: string,
  option: string
): string => {
  wrapper.find(`${selector} input`).instance().value = option.charAt(0)
  wrapper.find(`${selector} input`).simulate('change', {
    target: { value: option.charAt(0) }
  })
  wrapper
    .find(`${selector} .react-select__menu div[children="${option}"]`)
    .simulate('click')
  return `${selector} .react-select__single-value`
}

Usage:

const select = selectOption(
        component,
        '#country',
        'United States of America'
      )

Assert, where component is added to the test using mount():

expect(component.find(select).text()).toEqual('United States of America')

@ljharb simulate has a problem in fact. Sometimes it working but for one test simulate not trigger event change. Atfer many tests, with my team we are arrived to invoke onChange by using .invoke('onChange')() too.

@idoo can you share your test case? If wrapper.props().onChange(event) works then it should work with shallow since all simulate does with a shallow wrapper is map the event name the event handler in props (so 'change' maps to props.onChange)

This is also potentially an issue with react-select. It might not actually be registering change events the way you expect it to.