1.はじめに
create-react-app
で作成したappを、jestでテストする環境を作るのに、とても苦労したので、自分のためにも残します。
まだわからないことが多いですが、恥をさらして。
試行錯誤してできた環境「jest-prac-app」を元に、もう一度いちからcreate-react-app
して「jest-prac-app2」を作ってみたので、名前はそんな感じですが、たぶんほぼ必要十分です。
ソースはこちらです。
1.1.環境・バージョン
- Windows 10
- react: 18.2.0
- create-react-app: 10.2.3
-
npm create-react-app --version
で確認
-
1.2.ディレクトリ構成
下記のパターン1で、フォルダ名は「test」です。
jest-prac-app
├─ node_modules
├─ public
├─ src
├─ test
└─ package.jsonなどのファイル
2.テスト対象のコンポーネントを準備
2.1.create-react-app
$ npx create-react-app jest-prac-app2
$ cd jest-prac-app2
※ windowsですが、コマンドとわかりやすくするために $ を書いてます。
2.2. コンポーネント作成、配置
2.2.1. MyButton.jsを作成
クリックするとON/OFFが切り替わるボタンです。
さっそくここで、のちのテスト時にハマった点を1つ。1行目のReact
は省略できるんですが、入れないとテスト時にエラーになるので入れています。
import React, { useState } from "react"
const MyButton = () => {
const [isOn, setIsOn] = useState(false);
const handleClick = () => {
setIsOn((prevState) => !prevState);
};
return (
<button onClick={handleClick}>{isOn?"ON":"OFF"}</button>
);
}
export default MyButton;
2.2.2. index.jsからAppを外して、代わりにMyButtonを入れる
reportWebVitals関連は、今はいらないので消します。
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import MyButton from './MyButton';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<MyButton />
</React.StrictMode>
);
2.2.3. 不要ファイルを削除
App.js、App.test.js、App.cssは、放っておいてもいいけど、テストで動いたりするとめんどくさいので、ファイル削除。
2.3. appの動作確認
$ npx run start
ブラウザに出て、デフォルトOFFから、クリックするとON、OFFと切り替わる。htmlはbuttonタグです。
3. テスト環境の設定<本題>
さて、テスト環境。
3.1. package.json
テストをjestで行うために、"test"の行のテスト用コマンドを修正。
{
"name": "jest-prac-app2",
~ 略 ~
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "jest",
"eject": "react-scripts eject"
},
~ 略 ~
}
3.2. jest.config.jsを作成
-
testMatch
:テスト用スクリプトの場所- ディレクトリ構成のキモ。
-
moduleNameMapper
:importのファイルは、@
で始まってたら、rootDirを見てねという設定- 正規表現で置換するんですね。
()
の部分を$1
で使う形。
- 正規表現で置換するんですね。
-
setupFilesAfterEnv
:最初からある/src/setupTest.js
を参照する設定- 入れないと、
TypeError: expect(...).toHaveTextContent is not a function
という、関係性がわからない変なエラーが出る。
- 入れないと、
-
testEnvironment
:jsdom
は、今はわからない(要調査)
- とあるエラーが出たときに、
ChatGPTさんに教えてもらった。
- とあるエラーが出たときに、
module.exports = {
testMatch: ["<rootDir>/test/**/*.test.js"],
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/$1",
},
setupFilesAfterEnv: ["<rootDir>/src/setupTests.js"],
testEnvironment: 'jsdom',
};
3.3. Babel環境の設定
こちらも、今は十分理解できてないです。(要調査)
Babelのプリセットを設定します。(中略)Babelは、ES.nextの構文とJSXを、ブラウザが解釈できるコードに変換します。(O'Reilly Japan - Reactハンズオンラーニング 第2版)
$ npm install @babel/preset-env @babel/preset-react
babel.config.jsを作成します。
module.exports = {
presets: ["@babel/preset-env", "@babel/preset-react"],
};
3.4. @testing-library/user-eventをアップデート
あとで書くテストスクリプト中の、const user = userEvent.setup();
で、TypeError: _userEvent.default.setup is not a function
というエラーが出ました。そんなバカなって感じですが、latestを入れれば解決。
- 参考
どこかで相性問題がおこるかもしれないと目にしたので、/reactもlatestを入れます。
$ npm install @testing-library/react@latest @testing-library/user-event@latest
ちなみにバージョンは、私の環境では下記でした。
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@testing-library/react": "^14.2.0",
"@testing-library/user-event": "^14.5.2",
4. テストスクリプトの作成と実施
4.1. テストスクリプトの作成
1行目の、Reactは、コンポーネントと同様、ないとエラーが出ます。
テストの書き方などの詳細は割愛します。なおact()
についてまだ理解不足で、おそらく書き方がよくない。でも理解を避けては通れないと感じているところです。
import React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { act } from 'react-dom/test-utils';
import MyButton from "@/src/MyButton";
describe("myButtonのテスト", () => {
test("ボタンをクリックするとON/OFFが切り替わる", async () => {
// まず描画
act(() => {
render(<MyButton />);
});
// ユーザーを想定
const user = userEvent.setup();
// 初期状態をテスト
const myButton = screen.getByRole("button");
expect(myButton).toHaveTextContent("OFF");
// クリック
await act(async () => {
await user.click(myButton);
});
// 今度はONになる
expect(myButton).toHaveTextContent("ON");
});
it("itでもいいっていうだけのテスト", async () => {
// まず描画
render(<MyButton />);
// 初期状態をテスト
const myButton = screen.getByRole("button");
expect(myButton).toHaveTextContent("OFF");
});
test("描画されてすぐはOFFと表示されている", () => {
// 描画
const view = render(<MyButton />);
// スナップショットを取る
expect(view.container).toMatchSnapshot();
});
});
4.2. テスト実施
$ npm run test
> jest-prac-app2@0.1.0 test
> jest
PASS test/MyButton.test.js
myButtonのテスト
√ ボタンをクリックするとON/OFFが切り替わる (174 ms)
√ itでもいいっていうだけのテスト (13 ms)
√ 描画されてすぐはOFFと表示されている (7 ms)
› 1 snapshot obsolete.
• MyButtonのテスト スナップショットでテスト 1
Snapshot Summary
› 1 snapshot obsolete from 1 test suite. To remove it, run `npm test -- -u`.
↳ test/MyButton.test.js
• MyButtonのテスト スナップショットでテスト 1
Test Suites: 1 passed, 1 total
Tests: 3 passed, 3 total
Snapshots: 1 obsolete, 1 passed, 1 total
Time: 3.171 s
Ran all test suites.
OK!
jest-prac-app2/test/__snapshots__
に、スナップショットのファイルもできてました。
5. おわりに
バージョンアップが速いせいかなんかわかりませんが、ググっても正解なんだかそうでないんだかわからないし、聞ける先輩もいない中、試行錯誤で解を得られたことはまずよかったです。何度か諦めかけた・・・。やり始めてからこの記事を書き終えるまで、大体5時間くらいかかりました。
「create-react-app」で道を開拓して、「create-react-app2」できれいな道をもう一度通るという方法でしたが、それでもすんなりとはいかないです。自分が通った道(やったこと)を忘れてるだけなんですが。
さて。まだ知識不足のところはありつつも、テストの環境はできるようになったっぽいので、テストをエンジョイしたいです。みなさんも、良いテストコーディングを!