Reactのテストでactで包むのはrenderではなく状態更新
まず、ReactのuseStateで値を持って、それを表示するだけのコンポーネントを作る。
props.exportSetValueはsetValueをテストコードの側に露出させるためのコールバック。
code:MyComponent.tsx
import { useState } from "react";
export const MyComponent = (props: {
exportSetValue: (
setValue: React.Dispatch<React.SetStateAction<number>>
) => void;
}) => {
props.exportSetValue(setValue);
return <span>{value}</span>;
};
成功するテストケース1。冒頭の5行はsetValueを取り出すためのもの。
テストシナリオは「レンダリングして、0が表示されてることを確認し、setValue(1)して、1が表示されていることを確認する」というもの。
注目するところは、(1)のrenderがactでラップされておらず、 (2)のsetValueがactでラップされているところ。
code:test.ts
test("MyComponent1", () => {
type TSetState = React.Dispatch<React.SetStateAction<number>>;
let setValue: TSetState | undefined;
const exportSetValue = (s: TSetState) => {
setValue = s;
};
render(<MyComponent exportSetValue={exportSetValue} />); // (1)
expect(screen.getByText("0")).toBeTruthy();
expect(setValue).toBeTruthy();
act(() => {
setValue!(1); // (2)
});
expect(screen.queryByText("0")).toBeNull();
expect(screen.getByText("1")).toBeTruthy();
});
act()の解説には下記のようなサンプルコードが書いてあるが、ものすごくミスリーディング。 code:sample.ts
act(() => {
// render components
});
// make assertions
解説文章を読むと一応ユーザイベントもレンダーもユニットである、とは書いてある。
UI テストを記述する際、レンダー、ユーザイベント、データの取得といったタスクはユーザインターフェースへのインタラクションの「ユニット (unit)」であると考えることができます。react-dom/test-utils が提供する act() というヘルパーは、あなたが何らかのアサーションを行う前に、これらの「ユニット」に関連する更新がすべて処理され、DOM に反映されていることを保証します。
setValueがactでラップされていない場合に出る警告はもっとまともなことが書いてある。
When testing, code that causes React state updates should be wrapped into act(...)
「Reactの状態更新を引き起こすコードはactでラップせよ」
今回の例でなぜrenderをラップしないで良いかというと、それが状態更新を引き起こさないからだ
この警告メッセージに含まれるサンプルコードではactの中身のコメントが// render componentsではなく/* fire events that update state */になっている
ドキュメントの側もこちらにあわせるべきでは?
full warning:
code:output
Warning: An update to MyComponent inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
at MyComponent (/Users/nishio/keicho-webclient/src/MyComponent.tsx:8:29)
52 | // act(() => {
53 | setValue!(1);
| ^
ここから本題になるのだけど別のページに分ける