Open11

React Server Components のテストの仕方を調べながら、手を動かす

じょーだんじょーだん

最近、React Server Components (これ以降RSC)のテストをしたいと思い、調べます。

じょーだんじょーだん

以下のようなコンポーネントを用意

Sleep.tsx
import React, { Suspense } from "react";

const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

const Sleep = async () => {
  await sleep(2000);
  return (
    <>
      <div>おはよう!</div>
    </>
  );
};

export default Sleep;

じょーだんじょーだん

例えばコンポーネントをテストする場合、

testing-libraryのrenderを使うと思います。

以下でテストを行うと

/**
 * @jest-environment jsdom
 */
import { render, screen } from "@testing-library/react";
import Sleep from "app/components/Sleep";

describe("sleepのテスト", () => {
  it("期待する文字が表示されるか", async () => {

    render(<Sleep />);

    expect(screen.getByText("おはよう!")).toBeInTheDocument();
  });
});

じょーだんじょーだん
Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.

上記エラーが出てきてしまう。

じょーだんじょーだん

以下でpassできた。

/**
 * @jest-environment jsdom
 */
import { render, screen } from "@testing-library/react";
import Sleep from "app/components/Sleep";

describe("sleepのテスト", () => {
  it("期待する文字が表示されるか", async () => {
    const result = await Sleep();
    render(result);
    expect(screen.getByText("おはよう!")).toBeInTheDocument();
  });
});

じょーだんじょーだん

しかしこのテクニック注意点があるようで、

async componentsの中でasync componentsを使用するとエラーが起こってしまう。

Sleep.tsx
import React, { Suspense } from "react";
import DeepSleep from "./DeepSleep";

const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

const Sleep = async () => {
  await sleep(2000);
  return (
    <>
      <div>おはよう!</div>
      <Suspense fallback={<div>DeepSleepしてます</div>}>
        <DeepSleep />
      </Suspense>
    </>
  );
};

export default Sleep;

DeepSleep.tsx
import React from "react";

const deepSleep = (ms: number) =>
  new Promise((resolve) => setTimeout(resolve, ms));

const DeepSleep = async () => {
  await deepSleep(4000);
  return <div>超おはよう!</div>;
};

export default DeepSleep;

じょーだんじょーだん
e2e/sample.spec.ts
import { test, expect } from "@playwright/test";

test("期待する文字が表示されるか", async ({ page }) => {
  await page.goto("/");
  await expect(page.getByText(/^おはよう!$/)).toBeVisible();
  await expect(page.getByText(/^超おはよう!$/)).toBeVisible();
});

実行

npx playwright test 

実行結果

  ✓  1 [Mobile Chrome] › sample.spec.ts:3:5 › 期待する文字が表示されるか (7.7s)
  ✓  2 [Desktop Chrome] › sample.spec.ts:3:5 › 期待する文字が表示されるか (7.7s)
  ✓  3 [Mobile Safari] › sample.spec.ts:3:5 › 期待する文字が表示されるか (7.8s)

  3 passed (19.8s)
じょーだんじょーだん

3環境でテストを行なってくれました。
--headedオプションを追加して実行すると、実際のブラウザが立ち上がってテストが行われます。

テスト時間がかかりそうな印象を受けた。
コンポーネント単位というよりは、ページ単位のテストになっている。

Container/Presentational Components パターンを利用するテスト手法もあるそう。
https://azukiazusa.dev/blog/server-components-testing/#containerpresentational-components-パターン

https://zenn.dev/buyselltech/articles/9460c75b7cd8d1