How to set initial state value for useState Hook in jest and enzyme? How to set initial state value for useState Hook in jest and enzyme? reactjs reactjs

How to set initial state value for useState Hook in jest and enzyme?


Upon the answer you linked, you can return different values for each call of a mock function:

let myMock = jest.fn();myMock  .mockReturnValueOnce(10)  .mockReturnValueOnce('x')  .mockReturnValue(true);

In my opinion this is still brittle. You may modify the component and add another state later, and you would get confusing results.

Another way to test a React component is to test it like a user would by clicking things and setting values on inputs. This would fire the event handlers of the component, and React would update the state just as in real configuration. You may not be able to do shallow rendering though, or you may need to mock the child components.

If you prefer shallow rendering, maybe read initial state values from props like this:

function FooComponent({initialStateValue}) {  const [state, setState] = useState(initialStateValue ?? []);}


If you don't really need to test state but the effects state has on children, you should (as some comments mention) just then test the side effect and treat the state as an opaque implementation detail. This will work if your are not using shallow rendering or not doing some kind of async initialization like fetching data in an effect or something otherwise it could require more work.

If you cant do the above, you might consider removing state completely out of the component and make it completely functional. That way any state you need, you can just inject it into the component. You could wrap all of the hooks into a 'controller' type hook that encapsulates the behavior of a component or domain. Here is an example of how you might do that with a <Todos />. This code is 0% tested, its just written to show a concept.

const useTodos = (state = {}) => {  const [todos, setTodos] = useState(state.todos);  const id = useRef(Date.now());    const addTodo = useCallback((task) => {    setTodos((current) => [...current, { id: id.current++, completed: false, task }]);  }, []);  const removeTodo = useCallback((id) => {    setTodos((current) => current.filter((t) => t.id !== id));  }, []);    const completeTodo = useCallback((id) => {    setTodos((current) => current.map((t) => {      let next = t;              if (t.id === id) {        next = { ...t, completed: true };      }            return next;    }))  }, []);  return { todos, addTodo, removeTodo, completeTodo };};const Todos = (props) => {  const { todos, onAdd, onRemove, onComplete } = props;    const onSubmit = (e) => {    e.preventDefault();    onAdd({ task: e.currentTarget.elements['todo'].value });  }    return (    <div classname="todos">      <ul className="todos-list">        {todos.map((todo) => (          <Todo key={todo.id} onRemove={onRemove} onComplete={onComplete} />        )}      </ul>      <form className="todos-form" onSubmit={onSubmit}>        <input name="todo" />        <button>Add</button>      </form>    </div>  );};

So now the parent component injects <Todos /> with todos and callbacks. This is useful for testing, SSR, ect. Your component can just take a list of todos and some handlers, and more importantly you can trivially test both. For <Todos /> you can pass mocks and known state and for useTodos you would just call the methods and make sure the state reflects what is expected.

You might be thinking This moves the problem up a level and it does, but you can now test the component/logic and increase test coverage. The glue layer would require minimal if any testing around this component, unless you really wanted to make sure props are passed into the component.