So far:.
- 1: Wrapping in act in React tests is not render but state update
- 2: When updating the state with the result of a Promise, it is not possible to wrap the entire Promise in an act
I would like to test code in Jest where a new Promise is created by an event raised by the user, and then a React state update is performed on that then.
- For example, âWhen a user clicks a button, network access is performed and the result is drawn.
- Even if the network access part is replaced by a mock, it will still be asynchronous by Promise.
In such situations, the approach of âwaitFor until the expected element appearsâ is known, but with this approach, it is not possible to test the B side in cases such as âif the network response is A, show the menu; if B, donât show it. We need a way to know when redrawing is complete due to state update.
Try it with a simple component. The userTrigger
returns using Promise. (This is foreshadowing)
ts
The test scenario is â0 when first drawn, 0 immediately after the user triggers it, and 1 when the Promise is resolvedâ. The following writing style will fail. ts
The execution of then after resolve is always asynchronous, so the only way to guarantee that the line below it will be executed after the execution of then is to connect or await with then to the Promise that was created. (I moved it to a separate page because there was an example here earlier [Asynchronous It just happened to work, but itâs inappropriate.)
The following writing allows (2) to test the state after the asynchronous update caused by the user operation performed in (1) is completed ts
To wrap updates in setValue with act Replace useState. In the code below, after console.log goes out in order from 1 to 11, 2 to 4, or âredrawing componentsâ runs, and then 12 is displayed. MyAsyncComponent.tsx
import { useState } from "react";
export type TUserTrigger = () => Promise<unknown>;
export type TResolve = (value: number) => void;
export let userTrigger: TUserTrigger;
export let resolve: TResolve;
export const MyAsyncComponent = () => {
console.log(2);
const [value, setValue] = useState(0);
console.log(4);
userTrigger = () => {
console.log(6);
return new Promise<number>((res) => {
console.log(7);
resolve = res;
}).then((x) => {
console.log(10);
setValue(x);
});
};
return <span>{value}</span>;
};
My.test.ts
Now, we can control the process flow as expected. I was going to end with âhappily ever after,â butâŠ
userTrigger
returns using Promise. (This is foreshadowed by)
When creating a Promise in an event handler or useEffect, the Promise cannot be returned to the test code as a return value.
- useEffect specifies that âthe return value is a cleanup functionâ doc.
fireEvent.click
is a(element: ...) => boolean
.
I can think of two options.
- Export the created Promise itself
- Cut out the part that creates the Promise into a function, export it, mock it with jest, and extract the return value.
I do the former because the latter is a pain in the ass, but I guess itâs a matter of taste. Next time: Export Promise created with useEffect.
This page is auto-translated from /nishio/éćæăȘReactăźç¶æ æŽæ°ăăăčăăă using DeepL. If you looks something interesting but the auto-translated English is not good enough to understand it, feel free to let me know at @nishio_en. Iâm very happy to spread my thought to non-Japanese readers.