##はじめに
これは忘れないために残したメモのため、あまり参考にならないかもしれません。
そのためあまり当てにならないかもしれません。ご自身で実行して確認してみてください。
検索して見つけれれるメモとして利用予定です。
とりあえず下記のように覚えた!
・Jest
は、JavaScriptの全般的なテストに使う。
・@testing-library/react
は、Reactをテストするため最適化されたライブラリ。
※ create-react-appでReactプロジェクトを作るとどちらもインストールされるので、
今回は、Jest
+ react-testing-library
の組み合わせを使用する
##環境
Windows10
##バージョン
> yarn --version
1.22.10
> node --version
v14.14.0
##jestを使う準備
create react-appでプロジェクトを作成すると同梱されているらしい。準備は簡単!!
> yarn create react-app my-app --template typescript
yarn create v1.22.10
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
success Installed "create-react-app@4.0.3" with binaries:
- create-react-app
"C:\Program" は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。
error Command failed.
Exit code: 1
え。。
Nodistのインストール先がをデフォルトのC:\Program Files (x86)配下になっていたため、
なんか半角スペースが入ってしまうようで、正しくcreate react-appが実行できない。
c直下にインストールしなおして実行
ええ。。。
yarnをインストールしなおす。
下記は、nodeのバージョン12までじゃないと実行エラーになるが、yarn create react-app
実行時は、nodeのバージョン10.12.0 または 12.0.0 以上にしなさいと言われる。
> npm install --global yarn
成功!
> yarn create react-app my-app --template typescript
・・・
Success! Created my-app at C:\pleiades\workspace_jest\my-app
Inside that directory, you can run several commands:
yarn start
Starts the development server.
yarn build
Bundles the app into static files for production.
yarn test
Starts the test runner.
yarn eject
Removes this tool and copies build dependencies, configuration files
and scripts into the app directory. If you do this, you can’t go back!
We suggest that you begin by typing:
cd my-app
yarn start
Happy hacking!
Done in 81.70s.
##初めてjestを使う
(ようやく来た!)
この状態で yarn test
を実施。
はい、成功!!
自動で作られているsrc/App.test.tsx
のテストが実行される。
> yarn test
yarn run v1.22.10
$ react-scripts test
PASS src/App.test.tsx (28.644 s)
√ renders learn react link (63 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 51.465 s
##テストコードを自分で書く
###テストコード
デフォルトでは下記の二つがテストコードとして認識される。(少なくとも自分の環境では)
1.__tests__
フォルダ配下に配置
2.○○.test.tsx
や ○○.spec.tsx
ファイル名に.testなどを入れる ※.ts|.tsx|.js
※jest.config.js
に別のマッチ条件を書くこともできる
###テスト内容は、ボタンのクリックイベント確認と表示確認
import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import { Button } from "./Button"; // テスト対象のオリジナルボタンコンポーネント
describe("ボタン", () => {
test("表示", () => {
const testMock = jest.fn();
render(<Button onClick={testMock}>testButton</Button>);
const button = screen.getByText("testButton");
screen.debug();
// ラベル"testButton"が画面に表示されたことを確認
expect(button).toBeInTheDocument();
});
test("onClick", () => {
const testMock = jest.fn();
render(<Button onClick={testMock}>testButton</Button>);
const button = screen.getByText("testButton");
fireEvent.click(button);
// クリックイベントのファンクションが動くことを確認
expect(testMock).toHaveBeenCalled();
});
});
####上記内容の解説
・react-testing-libraryのrender
関数
テスト対象をレンダリングしテスト内でアクセス可能とできる。
・react-testing-libraryのdebug
関数
コンソール上にrenderで表示したHTML構造が表示される。取得可能な要素が分からない時に使える。
・react-testing-libraryのscreen.getByText
関数
レンダリングした画面から引数に渡したテキストの要素を探して取得する。
・react-testing-libraryのfireEvent.click
関数
引数にボタン要素を渡すとレンダリング中のボタン要素をクリックできる。
####その他使い方メモ
・screen.get*系
引数で指定した要素が存在しないとエラーとなる。
・screen.query*系
引数で指定した要素が存在なくともエラーとならない。※存在しない確認に使える。
・screen.getByText("探したいテキスト")
表示中の要素から引数で指定したテキストの要素を探す。
・screen.queryByText("")
やscreen.getByText("")
コンソール上に表示中のHTML構造が表示される。※debug()と違いエラーにはなるが、、
・screen.getByRole("")
下記のように親切に指定できる引数と取得対象を教えてくれる。
・・・
--------------------------------------------------
button: ← これが引数に指定できる 例)screen.getByRole("button");
Name "testButton":
<button
class="button"
/>
--------------------------------------------------
・・・
・beforeAll
すべてのテストが実行される前1回だけ実行される。例)fetchMockをリセットなどに使用
・afterAll
すべてのテストが実行された後1回だけ実行される。
・beforeEach
テストが実行される前に毎回実行される。
beforeEach(() => {
~ 内容 ~
});
・afterEach
テストが実行された後に毎回実行される。
##Hooks(useDispatch, useSelector)をモック化してテストコードを自分で書く
###テスト概要
あ、useDispatch
とuseSelector
を使用しているオリジナルのMadaiDialog
をテストするという想定のテスト
importの下あたりに書いておく
###まずこれ
// useDispatchのモック
const mockDispatch = jest.fn();
// useSelectorのモック
const mockSelector = jest.fn();
jest.mock("react-redux", () => ({
useDispatch: () => mockDispatch,
useSelector: () => mockSelector(),
}));
###んで、上記の後にテストを書く
・・・
describe("ダイアログ表示テスト", () => {
// 実行前にstateの内容を自分で設定
beforeEach(() => {
// モックの内容設定
mockSelector.mockImplementation(() => ({
contentMessage: [], // ← useSelector時に取得したいstateの内容を自由に設定
errorCode: null, // ← useSelector時に取得したいstateの内容を自由に設定
}));
});
test("表示", () => {
render(<MadaiDialog open={true} />);
const content = screen.queryByText("マダイ釣り教えます");
// 表示されたことを確認
expect(content).toBeInTheDocument();
});
test("非表示", () => {
render(<MadaiDialog open={false} onSelect={testMock}/>);
const content = screen.queryByText("マダイ釣りの方法");
// 表示されていないことを確認
expect(content).not.toBeInTheDocument();
});
});
・・・
####上記内容の解説と特徴
・describe
毎にモックの内容を変えたいテスト!!!
・MadaiDialog
コンポーネント内でuseDispatch
とuseSelector
を使っているため、モックが必要!!
・queryByText
を使用して.not
で表示されていないことを確認している!!!!
※getByTextだとエラーとなるよ。
※表示の有無は、open={false}
の設定で決まる使用のコンポーネントのため、モックを確認するテストとなっていない。
※モックを確認するテストは、下記。
###続いてモックを確認するテスト書く
・・・
describe("モックのテスト", () => {
beforeEach(() => {
// モックの内容設定
mockSelector.mockImplementation(() => ({
contentMessage: ["マダイ釣り教えます!!表示されていますでしょうか??" , "マダイ釣り教えます2!!"],
}))
})
test("contentMessage 表示", () => {
render(<MadaiDialog open={true} />);
const content = screen.getByText("マダイ釣り教えます!!表示されていますでしょうか??");
const content2 = screen.getByText("マダイ釣り教えます2!!");
// 表示されたことを確認
expect(content).toBeInTheDocument();
expect(content2).toBeInTheDocument();
});
test("contentMessage 非表示", () => {
render(<MadaiDialog open={false} />);
const content = screen.queryByText("マダイ釣り教えます!!表示されていますでしょうか??");
const content2 = screen.queryByText("マダイ釣り教えます2!!");
// 表示されていないことの確認
expect(content).not.toBeInTheDocument();
expect(content2).not.toBeInTheDocument();
});
});
・・・
####上記内容の解説と特徴
・モックで設定したメッセージが画面に表示されているかのテストとなっている。
・二つ目は、open={false}
を設定しダイアログが非表示状態にしているため、メッセージが非表示となっている確認をしている。
####その他使い方メモ
・mockReturnValueOnce
useSelectorを呼び出したときに返す値
を引数に受け取る。複数つなげて呼び出し回数に応じて返却値が順に変えられる。
mockSelector
.mockReturnValueOnce(1) // ← 1回目のuseSelectorの時に1が買える
.mockReturnValueOnce(2) // ← 2回目のuseSelectorの時に2が買える
.mockReturnValueOnce(3); // ← 3回目のuseSelectorの時に3が買える
・mockImplementationOnce
useSelectorを呼び出したときに使用される関数
を引数に受け取る。複数つなげて呼び出し回数に応じて実行関数を順に変えられる。
###続いてクリック動作による分岐表示のテスト書く
メッセージをクリックするとローカルstateが変更され、メッセージが消えて
マダイ釣りの極意(詳細内容)が表示される仕様のコンポーネントという想定のてすと
・・・
describe("クリックのテスト", () => {
beforeEach(() => {
// モックの内容設定
mockSelector.mockImplementation(() => ({
contentMessage: ["マダイ釣り教えます!!表示されていますでしょうか??""],
contentDetail: ["マダイ釣りの極意その1・・・・・・"],
}))
})
test("クリックしていないた場合", () => {
render(<MadaiDialog open={true} />);
const content = screen.queryByText("マダイ釣り教えます!!表示されていますでしょうか??");
const contentDetail = screen.queryByText("マダイ釣りの極意その1・・・・・・");
// 表示されていることを確認
expect(content).toBeInTheDocument();
// 表示されていないことを確認
expect(contentDetail).not.toBeInTheDocument();
});
test("クリックした場合", () => {
render(<MadaiDialog open={true} />);
const content = screen.queryByText("マダイ釣り教えます!!表示されていますでしょうか??");
fireEvent.click(content);
const contentDetail = screen.queryByText("マダイ釣りの極意その1・・・・・・");
// 表示されていないことを確認
expect(content).not.toBeInTheDocument();
// 表示されていることを確認
expect(contentDetail).toBeInTheDocument();
});
});
・・・
####上記内容の解説と特徴
・クリックしたときに表示内容が変わる仕様をテストしている。
その他は特に変わりなし
##ついでにActionのテストコードを自分で書く
メモだしな、、記事を分けるのもなあと思い。。ここに書きます!。
###テスト概要
APIのレスポンスをfetch-mock
を使ってモック化し、redux-mock-store
を使って評価するテスト
※あんまりよくわかっていない。
※とりあえず動かして詰まったところを紹介する
####とりあえず書いたやつ
import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk";
import fetchMock from "fetch-mock";
import { fetchContents } from "./action"; // ← テスト対象のアクション
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
describe("action", () => {
afterEach(() => {
// テストを実行毎にfetchMockをリセット
fetchMock.restore();
});
it("fetchContents SUCCESS", () => {
// fetchAPIのmock化
// これを使うためにnode-fetchを追加
// https://github.com/wheresrhys/fetch-mock/blob/master/docs/cheatsheet.md
fetchMock.postOnce("path:/api/fetchContents/getlist", {
headers: { "content-type": "application/json" },
body: {
"contentMessage": [
"マダイ釣り教えます!!表示されていますでしょうか??" ,
"マダイ釣り教えます2!!"
]
},
});
// 確認用
const expectedActions = [
{
type: "MADAI_LIST",
payload: {},
},
{
type: "MADAI_LIST_SUCCESS",
payload: {
contentMessage: [
"マダイ釣り教えます!!表示されていますでしょうか??" ,
"マダイ釣り教えます2!!"
]
},
},
];
const store = mockStore({});
return store.dispatch(fetchSuggestionNeeds(projectId))
.then(() => {
// console.log(JSON.stringify(store.getActions())); // 結果出力
expect(store.getActions()).toEqual(expectedActions);
});
});
it("fetchContents FAILURE", () => {
fetchMock.postOnce("path:/api/fetchContents/getlist", {
headers: { "content-type": "application/json" },
body: {
error: {
code: "ERROR_TEST",
message:"test_message",
}
},
status: 500,
});
~ 省略 ~
});
});
####上記内容の解説と特徴
・fetch-mock
を使うためにnode-fetch
を追加している
・テストごとにモックをリセットしている。同じパスで設定できるのは一つのようなので、リセットが必要ぽい
・postOnce
指定したパスに該当するAPIレスポンスをモック化している
※path指定のほかにもいろいろ指定方法があるようです。
・後は、APIレスポンスを受け取って加工された結果を評価している
##アプリケーションテストの考え方
https://blog.recruit.co.jp/rtc/2020/07/15/reactredux_app_test_strategy/
テストによって何が保証できているかを考える必要があります
なるほど、どこまでテストするのか決めて合わせないといけないのか
##最後に
最後まで見てくれたみんなありがとうございます!!
ご覧になって、いかがでしょうか?
初投稿にしては、なかなか良い記事となったと思います。
それでは、またいつか記事を書くその時に(^_^)ノ
調べる時間をくれて、ありがとう!!