はじめに
TDDで開発をしていく中で実装は上手く行っているのにテストが上手く行かないパターンってありませんか?
僕はそのとき結構むやみやたらにawaitやact、waitforをむやみやたらに入れてしまっているのとここで一度機能を確認しておきたいと思いました。
対象者
この記事は下記のような人を対象にしています。
- 駆け出しエンジニア
- プログラミング初学者
- TDDを取り入れている開発をしている方
await userEvent.clickでテストが失敗するパターン
使用バージョン
- "@testing-library/user-event": "^14.5.2"
dialogを表示させるflagがsetTimeoutによってラップされている場合
// 実装
function UserEventTryPage() {
const [isShowDialog, setIsShowDialog] = useState(false);
return (
<div>
<button
onClick={() => {
setTimeout(() => {
setIsShowDialog(true);
}, 1000);
}}
>
dialog open
</button>
{isShowDialog && <p>dialog title</p>}
</div>
);
}
// テスト
it("dialog openボタンが押されてから1秒後にdialog titleが見えている事", async () => {
render(<UserEventTryPage />);
expect(screen.queryByText("dialog title")).not.toBeInTheDocument();
await userEvent.click(
screen.getByRole("button", { name: "dialog open" }),
);
expect(screen.getByText("dialog title")).toBeInTheDocument();
});
await userEventではその時に発生する非同期やUIのレンダリングは待ってくれるが
非同期タイマー処理は待ってくれないためテストが失敗する
ではこのような場合どうすればいいか?
- useFakeTimersを使用してタイマーを偽物に置き換えて手動で時間を経過させる方法
https://qiita.com/engineer_ponpon/items/dfe8473ea4769a65174c - 下記のようにexpectに await waitforを入れて要素が見えるまで待ってもらう
it("dialog openボタンが押されてから1秒後にdialog titleが見えている事", async () => {
render(<UserEventTryPage />);
expect(screen.queryByText("dialog title")).not.toBeInTheDocument();
await userEvent.click(screen.getByRole("button", { name: "dialog open" }));
await waitFor(()=>{
expect(screen.getByText("dialog title")).toBeInTheDocument();
})
});
注意
onClick内がsetTimeoutとstateの更新だけだとテストが失敗しました。
なんでもいいのですがconsole.log()など何かしら入れないとテストが成功しませんでした。
もしかすると現時点でのバグかもしれません。
<button
onClick={() => {
console.log()
setTimeout(() => {
setIsShowDialog(true);
}, 1000);
}}
>
dialog open
</button>
await userEvent.clickでテストが成功するパターン
非同期の処理がある場合
Promiseの非同期な処理が入っている場合はその処理を待ってくれる
function UserEventTryPage() {
const [isShowDialog, setIsShowDialog] = useState(false);
return (
<div>
<button
onClick={async () => {
await returnPromise();
setIsShowDialog(true);
}}
>
dialog open
</button>
{isShowDialog && <p>dialog title</p>}
</div>
);
}
it("dialog openボタンが押されてから1秒後にdialog titleが見えている事", async () => {
render(<UserEventTryPage />);
expect(screen.queryByText("dialog title")).not.toBeInTheDocument();
await userEvent.click(
screen.getByRole("button", { name: "dialog open" }),
);
expect(screen.getByText("dialog title")).toBeInTheDocument();
});
おわりに
await userEventは非同期を待ってくれるものという認識でしたが、
Promiseのような非同期関数の処理は待ってくれるものの、
タイマーが絡んでいる非同期な処理は待ってくれない事がわかった
最後まで読んで頂きありがとうございました。
少しでも参考になったと思ったらいいね!お願いします