I know this question is already asked here: How to set initial state for useState Hook in jest and enzyme?
const [state, setState] = useState([]);
And I totally agree with Jimmy's Answer to mock the useState function from test file but I have some extended version of this question, "What if I have multiple useState statements into the hooks, How can I test them and assign the respective custom values to them?"
I have some JSX rendering with the condition of hook's state values and depending on the values of that state the JSX
is rendering.
How Can I test those JSX by getting them into the wrapper
of my test case code?
I know this question is already asked here: How to set initial state for useState Hook in jest and enzyme?
const [state, setState] = useState([]);
And I totally agree with Jimmy's Answer to mock the useState function from test file but I have some extended version of this question, "What if I have multiple useState statements into the hooks, How can I test them and assign the respective custom values to them?"
I have some JSX rendering with the condition of hook's state values and depending on the values of that state the JSX
is rendering.
How Can I test those JSX by getting them into the wrapper
of my test case code?
- 2 If the value is set directly, without any external property (like a parent received prop), then I don't think there should be a reason to test different initial values; but I could definitely be wrong about it. Could you share an example of a ponent with a hook like the one you are trying to test? – Alvaro Commented Feb 13, 2020 at 12:25
- I can provide an example: say your state is updated in a child ponent, but used and defined in the parent ponent you are testing, if you shallow mount the parent there is no way to trigger a state update, so using an initial state could help with covering all parent ponent branches. – Ioan-Radu Tanasescu Commented Jun 4, 2020 at 13:09
-
I would rethink the testing on this situation, if you have a button that fires a
setState
trigger it, then if you have a paragraph that renders that state, assert it. – alextrastero Commented Oct 20, 2020 at 8:51
2 Answers
Reset to default 2Upon 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 ponent and add another state later, and you would get confusing results.
Another way to test a React ponent is to test it like a user would by clicking things and setting values on inputs. This would fire the event handlers of the ponent, 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 ponents.
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 ments 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 pletely out of the ponent and make it pletely functional. That way any state you need, you can just inject it into the ponent. You could wrap all of the hooks into a 'controller' type hook that encapsulates the behavior of a ponent 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++, pleted: false, task }]);
}, []);
const removeTodo = useCallback((id) => {
setTodos((current) => current.filter((t) => t.id !== id));
}, []);
const pleteTodo = useCallback((id) => {
setTodos((current) => current.map((t) => {
let next = t;
if (t.id === id) {
next = { ...t, pleted: true };
}
return next;
}))
}, []);
return { todos, addTodo, removeTodo, pleteTodo };
};
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 ponent injects <Todos />
with todos and callbacks. This is useful for testing, SSR, ect. Your ponent 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 ponent/logic and increase test coverage. The glue layer would require minimal if any testing around this ponent, unless you really wanted to make sure props are passed into the ponent.