How to properly test controlled input component which depend on useState with jest/enzyme

Viewed 1381

I want to test controlled component using Jest/enzyme.

I have an <Input> component that given max and min props will autocomplete the value when change event is triggered

// Input.tsx
const Input = ({value, onChange, max, min}) => {

  const handleChange = (evt) => {
    const newValue = Number(evt.target.value)

    if(min !== undefined && newValue < min) {
      onChange(min)
      return 
    }

    if(max !== undefined && newValue > max) {
      onChange(max)
      return
    }

    onChange(newValue)
  }

  return (
    <div>
      <label>My input</label>
      <input type="number" value={value} onChange={handleChange} />
    </div>
  )
}

I would like to test the component and its different change use-case with jest.
Because it's a fully controlled component I start to write the following code:

    const onChangeSpy = jest.fn()
    const Wrapper = () => {
      const [state] = React.useState(0)
      return (
        <Input
          value={state}
          onChange={onChangeSpy}
          min={0}
          max={50}
        />
      )
    }

    const wrapper = mount(<Wrapper />)
    wrapper
      .find('input')
      .simulate('change', { target: { value: 25 } })
      .simulate('change', { target: { value: 115 } })
    expect(onChangeSpy.mock.calls).toHaveLength(2) // pass

But I am running in an issue: The value prop of the <Input> is never updated because I never use the React.setState. My example is dumb and I may not need it, but in more complex real world scenario, I would like to setup some state by triggering events and then either test my state value or my onChangeSpy

How could I achieve this goal? Updating the state and be able to test it?

Update

I found a workaround that works for me.
It combines useState and jest.fn()

    const onChangeSpy = jest.fn()
    const Wrapper = () => {
      const [state, setState] = React.useState(0)
      return (
        <Input
          value={state}
          // Call both spy and setState (not clean but working)
          onChange={(val) => {
            onChangeSpy(val)
            setState(val)
          }}
          min={0}
          max={50}
        />
      )
    }

    const wrapper = mount(<Wrapper />)
    wrapper
      .find('input')
      .simulate('change', { target: { value: 25 } })
      .simulate('change', { target: { value: 115 } })
    expect(onChangeSpy.mock.calls).toHaveLength(2) // pass
    expect(wrapper.find('input').props().value).toEqual('115') // pass

0 Answers
Related