I tried everything but still for some reason the react state doesn't change, what is I am missing or what is the alternative approach? Code:
export const useCounter = (): UseCounterResult => {
const session = useSession();
const [list, setList] = useState<Customer[]>([]);
const [counter, setCounter] = useState(0);
const [limit, setLimit] = useState(10);
useEffect(() => {
(async () => {
const response = await getApiClient(session).listCustomers();
setList((response?.customers || []));
setCounter(response.todayCount);
setLimit(response.dailyLimit);
})();
}, [session]);
function addToList(newItem: Customer) {
setList((list) => [...newItem,...list]);
setCounter((counter) => counter + 1);
}
return {counter, limit, list, addToList};
};
And the UT I tried but doesn't work:
it.only("should add to list", async () => {
// arrange
(getApiClient as jest.Mock).mockReturnValue({
listCustomers: async () => ({
customers: [],
todayCount: 1,
dailyLimit: 5,
}),
});
// act
const { result, unmount, waitForNextUpdate, rerender } =
renderHook(useCounter);
act(() => {
result.current.addToList({
mobileNumber: "John",
receiverName: "+61456789123",
});
});
// await waitForNextUpdate();
// assert
await waitFor(() => expect(result.current.counter).toBe(2));
unmount();
});
I tried without wrapping into act
and instead calling waitForNextUpdate
after it, still didn't work, result.current.counter
is 1 instead of 2
.
Don't call addToList
until after you know that your async API call inside your useEffect
completes - because when that call completes it will overwrite the state that may have been set by an addToList
call if the async call happened to be in progress.
You can do that by asserting that result.current.dailyLimit
contains the value returned from the API.
After your test has asserted that, then go ahead and make your call to addToList
and perform your subsequent assertion
it.only("should add to list", async () => {
// arrange
(getApiClient as jest.Mock).mockReturnValue({
listCustomers: async () => ({
customers: [],
todayCount: 1,
dailyLimit: 5,
}),
});
// act
const { result, unmount, waitForNextUpdate, rerender } =
renderHook(useCounter);
// now you know your async call has resolved
await waitFor(() => expect(result.current.dailyLimit).toEqual(5));
// call add to list - you don't need to wrap it in `act` because you have `waitFor` right after
result.current.addToList({
mobileNumber: "John",
receiverName: "+61456789123",
});
await waitFor(() => expect(result.current.counter).toBe(2));
unmount();
});