3
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?

UniposAdvent Calendar 2024

Day 3

Vitest, React Testing Library, MSWなどを使ってみてハマったポイントについて

Posted at

この記事は、Unipos Advent Calendar 2024 の3日目の記事です。


WebフロントエンドのテストをVitest, React Testing Library, MSWなどを使って書いていた時にハマったケースについてここにまとめておきます。

複数のSuspenseしているコンポーネントがある時に全てを待ちたい

例えば、テストしたいコンポーネントの中に、複数のSuspenseしているコンポーネントが含まれているとします。Suspenseのfallbackにて、role="status"が設定してあるローディングのコンポーネントもあるとします。この場合に全てのSuspenseが終了するまで待ちたいというケース。

await waitForElementToBeRemoved(() => screen.getAllByRole('status'));

userEvent.clickの完了で通信も一緒に終わってしまう場合を避けたい

例えば、あるボタンがあり、それをクリックすると通信が発生するとします。そのボタンのクリックをテストで書くと、await userEvent.clickになりますが、一緒に通信も終わる扱いになってしまい、通信中になっているかどうかを判定できなくなるみたいなケース。

it('userEvent.clickの完了で通信も一緒に終わってしまう場合を避けたい', async () => {
  expect.hasAssertions();

  vi.useFakeTimers({
    toFake: ['setTimeout'],
    shouldAdvanceTime: true,
  });
  server.use(
    http.get('/book/:bookId', async () => {
      // NOTE: userEvent.clickでチェックをつける時に、通信も一緒に実行されるのを防ぐため、遅延を入れる。
      // 秒数はfakeTimerを利用しているのでそれなりに遅延があればなんでも良い。
      await delay(10000);
      return new Response()
    }),
  );

  render(<Component />)

  // ボタンのクリックだけは終わらせる
  await userEvent.click(screen.getByRole('button'));

  expect(~~~); // 通信中の状態

  // タイマーを進めて通信を完了扱いにする
  await act(async () => {
    await vi.runOnlyPendingTimersAsync(); // ここは場合によって違うかも
  });

  expect(~~~); // 通信後の状態

  vi.useRealTimers();
});

実装の都合上、確定でconsole.errorが出てしまう場合にログを出ないようにしたい

これで強制的に非表示にはできるが根本的にはよくなさそうです。

vi.spyOn(console, 'error').mockImplementation(() => {});

文字列の配列があり、要素ごとに指定した文字列が文中にあるかどうかだけ確認したい場合

例えば、~~~タイトル1~~~みたいな感じでテキストを取得してきた場合に、タイトルn(nに数値が入る)という部分だけあれば良いんだがということを確認したいケース。

expect(stringArray.map((longText) => longText.textContent)).toStrictEqual(
  expect.arrayContaining([
    expect.stringContaining('タイトル1'),
    expect.stringContaining('タイトル2'),
    expect.stringContaining('タイトル3'),
  ]),
);

現在の時刻をモックしたい

こういう感じに書けば良いことがわかりました。

vi.spyOn(Date, 'now').mockReturnValue(new Date('2022-12-01T00:00:00').getTime());

下記の方法だと、モックできるっぽいんですが、テスト実行中に現在時刻がモックした時間から経過するようでした。

vi.setSystemTime(new Date('2022-12-01T00:00:00').getTime());

そもそもscreen使った方がいいのか

renderの戻り値を使った方がいいのか、importしてきたscreenを使った方が良いのか分からない時期がありました。

そういう形におすすめ下記の翻訳記事

setTimeoutの中にPromiseの関数があったとき

どうもタイマーが進まないと思ったら利用する関数が違うらしい。公式ドキュメントは大事。

await act(async () => {
  await vi.advanceTimersByTimeAsync(1000);
});

React Testing Libraryの screen.debug() でたくさん行を表示したい

割と行数が短くてどんな状態か分からないことが結構あったんですが、debug関数の第二引数を変更することで対応できるようです。

screen.debug(undefined, Infinity);

事前にレンダリングしていたものが、何らかの通信後、消えたかどうか確かめたい

先にwaitForElementToBeRemovedをしておいて、通信が発生する処理を跨いだ後に、waitForElementToBeRemovedをawaitしておけば良いみたいです。

const isRemovedPromise = waitForElementToBeRemoved(screen.getByRole("dialog"));
await userEvent.click(screen.getByRole('button'); // 通信が発生する
await isRemovedPromise;

waitForElementToBeRemoved(screen.getByRole("dialog"))部分ですが、waitForElementToBeRemoved(() => screen.getByRole("dialog"));とした時に動作することあったので、どういう原理なのか確かめたいです。

参考にしたもの

3
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
3
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?