Enzyme wrapper.update() causes ref input to no longer have value prop

Viewed 470

Here is a Code Sandbox that contains a test simulating this issue. The test in this Code Sandbox fails as described in this question: https://codesandbox.io/s/react-jest-and-enzyme-testing-c7vng


I'm trying to test the value of an <input /> that gets updated inside a useEffect. This is the code from the Code Sandbox, which is a simplified version of something I'm trying to do in a project.

import React, { useEffect, useRef, useState } from "react";

const App = () => {
  const [count, setCount] = useState(0);

  useEffect(() => {
    ref.current.value = "";
    console.log(typeof ref.current.value);
  }, [count]);

  const ref = useRef(null);

  const handleClick = () => {
    setCount(count + 1);
    console.log(count);
  };

  return (
    <div>
      <input ref={ref} type="text" />
      <button onClick={handleClick}>click me</button>
    </div>
  );
};

export default App;

I use useRef to set the value of the <input />.

The useEffect gets called when the <button /> is clicked. The <button /> updates the useState count. useEffect is watching updates to count, and it gets called as a side-effect.

In the useEffect, I set ref.current.value to an empty string, and then I log the typeof this value to verify that it's a string.

In the test, I try to simulate this behavior:

describe("App", () => {
  const wrapper = mount(<App />);
  wrapper.find("input").props().value = "hello";
  act(() =>
    wrapper
      .find("button")
      .props()
      .onClick()
  );
  console.log(wrapper.find("input").debug());
  wrapper.update();
  console.log(wrapper.find("input").debug());
  expect(wrapper.find("input").length).toBe(1);
  expect(wrapper.find("input").props().value).toBe("hello");
});

I set the value prop to 'hello'. I then invoke the <button /> onClick prop, effectively clicking it. I then call wrapper.update(), and I also debug() log the <input /> before and after update().

Before, update(), the <input /> has a value prop containing 'hello'. After the update(), the <input /> does not have a value prop. This causes the test to fail, saying that the <input /> value is undefined after the update.

Shouldn't the input's value be '' after the update?

1 Answers

Here is a list of issues with the current way:

  • <input ref={ref} type="text" /> is describes a React Element and has no value prop

  • Prop value should be controlled via state and not mutated directly

    wrapper.find("input").props().value = "hello";
    
  • Setting value on a DOM Node isn't the same as setting a prop value. Using React means that you ceed DOM manipulation to it. useRef allows for access to the underlying DOM Node when passed an initial value of null and this following line mutates DOM in spite of App state.

    ref.current.value = "";
    

In certain scenarios, it's expedient to manipulate DOM in spite of the App state. The tests should then deal with the DOM Node and check changes to it.

describe("App", () => {
  const wrapper = mount(<App />);
  wrapper.find("input").getDOMNode().value = "hello";
  act(() =>
    wrapper
      .find("button")
      .props()
      .onClick()
  );
  wrapper.update();
  expect(wrapper.find("input").length).toBe(1);
  expect(wrapper.find("input").getDOMNode().value).toBe("");
});

If you consider that your use case doesn't require this much of a direct control of the DOMNode. The input element value prop can be controlled with state. For example,

const App = () => {
  const [count, setCount] = useState(0);
  const [value, setValue] = useState("hello");
  useEffect(() => {
    setValue("");
  }, [count]);

  const handleClick = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <input type="text" value={value} />
      <button onClick={handleClick}>click me</button>
    </div>
  );
};

export default App;
Related