Closed11

【key-front】react-testing-libraryの素振りをしてみた(前編)

1zushun1zushun

モチベーション

  • 毎週金曜日Slackのkey_frontチャンネルでハドル機能を使いお題に対してメンバー同士ディスカッションをする時間を15〜30分程度設けている
  • 今回は「react-testing-libraryを触ってみて」の共有会
  • ファシリテーターは筆者なので、事前に読み込んで気になった点などをスクラップに投げていく
  • 開催日は9/21(木)で最終的に議事録として結論をまとめる

後編

https://zenn.dev/shuuuuuun/scraps/69ac109126b60e

1zushun1zushun

メモ1

  • 普段はjest-puppeteerとかCypressのようなe2eテストを書くことが多い(けど有名なテストトロフィーだとe2eの優先度は低い)
  • なぜかjest-puppeteerに関するZennの記事が少ないから誰かの助けになるかもと思っていくつか記事を作っていた。

https://zenn.dev/search?q=jest-puppeteer

  • 実際にe2eをやってるけどクリティカルなところがメインになる。ログインとか購入導線とかそもそも落ちたらまずいよね。というところ。
  • 業務委託でreact-testing-libraryを触っているけれど、一旦公式とかに沿って改めてテストコードを書いていきたいと思ってお題にした。

Next.jsの公式ドキュメントにもあるようにテストを試せるテンプレートが用意されているので、これを骨子にいろいろ試していく

npx create-next-app@latest --example with-jest with-jest-app

create-next-app で with-jest のテンプレートを利用すると、Jest と React Testing Library をすぐに使い始めることができます

Next.js 英語

https://nextjs.org/docs/pages/building-your-application/optimizing/testing#jest-and-react-testing-library

Next.js 日本語

https://nextjs-ja-translation-docs.vercel.app/docs/testing

1zushun1zushun
test('loads and displays greeting', async () => {
  // Arrange
  // Act
  // Assert
})
  • ArrageにはReact要素をDOMにレンダリングするrenderメソッドを配置する
  • Actにはユーザーのアクションをシミュレートするためのイベントを発生させる
  • Assertにはテストの結果(期待値)を書く
具体例
// Arrage
render(<Fetch url="/greeting" />)
// Act
fireEvent.click(screen.getByText('Load Greeting'))
await screen.findByRole('alert')
// Assert
expect(screen.getByRole('alert')).toHaveTextContent('Oops, failed to fetch!')
expect(screen.getByRole('button')).not.toBeDisabled()

https://testing-library.com/docs/react-testing-library/example-intro#full-example

react-testing-libraryのexampleで登場するmswに関して補足

https://zenn.dev/takepepe/articles/msw-driven-development

1zushun1zushun

メモ2

with-jest のテンプレートでもデフォルトで入っているテストコードもArrange、Act、Assertでテストが書かれている

import { render, screen } from '@testing-library/react'
import Home from '@/pages/index'

describe('Home', () => {
  it('renders a heading', () => {
    render(<Home />)

    const heading = screen.getByRole('heading', {
      name: /welcome to next\.js!/i,
    })

    expect(heading).toBeInTheDocument()
  })
})

1zushun1zushun
  • it関数やtest関数を1つのファイルに複数記述することができる
  • it関数やtest関数を複数含めることができるdescribe関数がある
  • describe関数のブロックはTest Suitesと呼ばれる
  • test/it関数のブロックは Test(Test Case)と呼ばれる
// テストスイート
describe("true is truthy and false is falsy", () => {
  // テストケース
  test("true is truthy", () => {
    expect(true).toBe(true);
  });
  // テストケース
  it("false is falsy", () => {
    expect(false).toBe(false);
  });
});

  • screen.debug();でデバッグすることができる
  • テストで使えるconsole.log();みたい
describe("Home", () => {
  it("renders a heading", () => {
    render(<Home />);
    screen.debug();
    const heading = screen.getByRole("heading", {
      name: /welcome to next\.js!/i,
    });

    expect(heading).toBeInTheDocument();
  });
});

1zushun1zushun

要素取得

  • getByText
  • queryByText
  • findByText
import { render, screen } from "@testing-library/react";
import App from "@/pages/01_sample";

describe("try getByText, queryByText, findByText", () => {
  // NOTE:テキストから要素取得
  it("try getByText", () => {
    render(<App />);
    const element = screen.getByText("Hello");
    expect(element).toBeInTheDocument();
  });

  // NOTE:取得できない場合はnullを返す(処理がストップしない)
  it("try queryByText", () => {
    render(<App />);
    const element = screen.queryByText("hey");
    expect(element).not.toBeInTheDocument();
  });

  // NOTE:findByTextの返り値がPromiseなのでasync/awaitが必要になる
  it("try renders Hello", async () => {
    render(<App />);
    const element = await screen.findByText("Hello");
    expect(element).toBeInTheDocument();
  });
});

  • getAllByText
import { render, screen } from "@testing-library/react";
import App from "@/pages/02_sample";

it("try getAllByText", () => {
  render(<App />);

  // NOTE:配列で取得する(複数取得することができる)ターゲットはhtmlタグではなくテキスト
  const elements = screen.getAllByText("Hello");
  expect(elements).toHaveLength(2);
});

  • getAllByRole
import { render, screen } from "@testing-library/react";
import App from "@/pages/03_sample";

// NOTE: https://www.w3.org/TR/html-aria/#docconformance
it("try getAllByRole", () => {
  render(<App />);
  // htmlタグ(厳密にはrole)で複数取得する
  const listElements = screen.getAllByRole("listitem");
  expect(listElements).toHaveLength(3);
});

  • getByTestId
import { render, screen } from "@testing-library/react";
import App from "@/pages/04_sample";

it("try getByTestId", () => {
  render(<App />);
  // NOTE:Cypressのdata-cyと同じ感じ
  const element = screen.getByTestId("test");
  expect(element).toBeInTheDocument();
});

https://docs.cypress.io/guides/references/best-practices#Selecting-Elements

1zushun1zushun
jest
npm install jest --save-dev
npm install @types/jest --save-dev
npm install ts-jest --save-dev
testing-library
npm install @testing-library/react --save-dev
npm install @testing-library/jest-dom --save-dev 
npm install @testing-library/user-event --save-dev
npm install --save-dev jest-environment-jsdom

React18の場合は@testing-library/react-hooksはインストールしなくてok(下記参照)

https://zenn.dev/k_kazukiiiiii/articles/9f48bdd20435d2

jest.config.json
{
  "roots": ["<rootDir>/resources/js"],
  "testMatch": [
    "**/__tests__/**/*.+(ts|tsx|js)",
    "**/?(*.)+(spec|test).+(ts|tsx|js)"
  ],
  "transform": {
    "^.+\\.(ts|tsx)$": "ts-jest"
  },
  "testEnvironment": "jest-environment-jsdom",
  "setupFilesAfterEnv": ["<rootDir>/jest.setup.ts"]
}
jest.setup.ts
// Optional: configure or set up a testing framework before each test.
// If you delete this file, remove `setupFilesAfterEnv` from `jest.config.js`

// Used for __tests__/testing-library.js
// Learn more: https://github.com/testing-library/jest-dom

import '@testing-library/jest-dom'

stylelint.json
"ignoreFiles": ["vite.config.ts", "jest.setup.ts"], // jest.setup.tsを追加
tsconfig.json
{
  "compilerOptions": {
    "types": ["react", "vite/client", "jest"] // jestを追加
  },
  "include": ["resources/js/**/*", "jest.setup.ts"], // jest.setup.tsを追加
}
package.jsonに追加
 "test": "jest",
 "test:watch": "jest --watch",
 "test:ci": "jest --ci"

lint-stagedはstagingに上がっているファイルが対象になるのでpre-commitで実行する。コミット毎にtest実行するとスピード感持って実装できなくなるのでpre-pushのタイミングでtestを実行する。本当はCIに組み込むのがベター。

.husky/pre-push
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm run test

1zushun1zushun

議事録_20230921

  • 9/21(木)に実施
  • 結論を出すコンテンツではなかったため特に結論はなし
  • 参加人数は6名(以下エビデンス)

このスクラップは2023/09/21にクローズされました