0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

await userEventはどこまで待ってくれるのか

Posted at

はじめに

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のレンダリングは待ってくれるが
非同期タイマー処理は待ってくれないためテストが失敗する
ではこのような場合どうすればいいか?

  1. useFakeTimersを使用してタイマーを偽物に置き換えて手動で時間を経過させる方法
    https://qiita.com/engineer_ponpon/items/dfe8473ea4769a65174c
  2. 下記のように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のような非同期関数の処理は待ってくれるものの、
タイマーが絡んでいる非同期な処理は待ってくれない事がわかった

最後まで読んで頂きありがとうございました。
少しでも参考になったと思ったらいいね!お願いします

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?