こんにちは。Photoraitエンジニアのヒエイです。
PhotoraitではWebつくというWebサイト作成ツールを提供しており、Reactを使用しています。
フロントエンドテストを導入した話を書きます。
前提
Photorait Webつく技術スタック
React 16.13 TypeScript 3.9
Jest導入
方針
パッケージインストール
npm install --save-dev jest @types/jest ts-jest enzyme enzyme-adapter-react-16 @types/enzyme @types/enzyme-adapter-react-16
初期設定
- React・Typescriptで実行のための初期設定
tsconfig.jest.json
- Jest実行用に別で用意。アプリケーション用tsconfigを上書きさせる形にしました。以下はサンプルです。
{ "extends": "./tsconfig.json", "compilerOptions": { "moduleResolution": "node", "allowJs": true, "noEmit": true, "strict": true, "jsx": "react", "baseUrl": ".", "paths": { "~/*": ["ソースのあるディレクトリ/*"], }, "lib": ["ES2019", "dom"], "module": "commonjs", "target": "ES2019", "resolveJsonModule": true }, "exclude": [ "node_modules" ] }
jest.config.js
- Jest用設定ファイルを用意
const { pathsToModuleNameMapper } = require('ts-jest/utils'); const { readFileSync } = require('fs'); const { parse } = require('jsonc-parser'); const { compilerOptions } = parse(readFileSync('./tsconfig.jest.json').toString()); const moduleNameMapper = pathsToModuleNameMapper(compilerOptions.paths, { prefix: '<rootDir>/' }); module.exports = { preset: 'ts-jest', globals: { 'ts-jest': { tsconfig: 'tsconfig.jest.json', diagnostics: false } }, roots: ["<rootDir>/ソースのあるパス"], transform: { "^.+\\.(ts|tsx)$": "ts-jest", }, transformIgnorePatterns: [ "/node_modules/*" ], testRegex: "(/__tests__/.*|\\.(test|spec))\\.(ts|tsx|js)$", testPathIgnorePatterns: [ "/node_modules/" ], moduleNameMapper, moduleFileExtensions: [ "ts", "tsx", "js", "json" ]
実行
package.json
を修正
"scripts": { ・・・ "test": "jest" }, ・・・
実行は以下
npm run test
テストコード設置
テスト対象の簡単な入力フォームを用意しました。
import React, { FormEvent, useState } from 'react' import { Button, Form, Header, Input } from 'semantic-ui-react' type Props = { text: string postRequest: (formValue: string) => void } const FormComponent: React.FC<Props> = ({ text, postRequest }) => { const [formValue, setFormValue] = useState(text) const handleChange = (changeValue: string): void => { setFormValue(changeValue) } const handleSubmit = (e: FormEvent<HTMLFormElement>): void => { e.preventDefault() postRequest(formValue) } return ( <> <Header as="h2">サンプルフォーム</Header> <Form onSubmit={(e): void => handleSubmit(e)}> <label htmlFor="sample_text">テキスト</label> <Form.Field width={8}> <Input id="sample_text" type="text" name="sample_text" placeholder="テキストを入力" value={formValue} onChange={(e): void => handleChange(e.target.value)} /> </Form.Field> <Button> 送信 </Button> </Form> </> ) } export default FormComponent
テキストボックスに入力した内容をform submitで送るコンポーネントです。
postRequest
には送信メソッドを設定します。
jestテストコードを簡単には以下の形で用意します。
import React from 'react' import { mount, configure } from 'enzyme' import Adapter from 'enzyme-adapter-react-16' import FormComponent from '~/components/form' configure({ adapter: new Adapter() }) const formPostRequest = jest.fn() describe('sample form test', () => { test('form submit test', () => { const inputText = 'テキストテキストテキスト' const wrapper = mount( <FormComponent text="" postRequest={formPostRequest} /> ) wrapper .find('input') .find({ name: 'sample_text' }) .simulate('change', { target: { value: inputText } }) wrapper.find('form').simulate('submit') expect(formPostRequest).toHaveBeenCalledWith(inputText) }) })
解説
enzyme読み込み
import React from 'react' import { mount, configure } from 'enzyme' import Adapter from 'enzyme-adapter-react-16' configure({ adapter: new Adapter() })
React 16 を利用する際の読み込みはこちらを参考
mock化
const formPostRequest = jest.fn()
アタッチするpostリクエスト用関数をjestのmock関数で用意します。
Jestテスト
const wrapper = mount( <FormCompornent text="" postRequest={formPostRequest} /> )
フォームコンポーネントを展開します。
const inputText = 'テキストテキストテキスト' wrapper .find('input') .find({ name: 'sample_text' }) .simulate('change', { target: { value: inputText } })
inputテキストボックスに文字列を入力しています。
wrapper.find('form').simulate('submit') expect(formPostRequest).toHaveBeenCalledWith(inputText)
フォームをサブミットさせ、アタッチしたformPostRequest
関数に入力した文字列通り渡ってくるかテストしています。
懸念点・要調査
-
- React Hooksの状態をEnzyme側で取得ができず、状態テストを上手く行えないようでした。
- 状態変更後のエレメントの数の変化等でテストを入れる事にしています。
まとめ
簡潔にも汎用的にもテストを仕込めるのですごく便利でした。
次回はstorybook
とスナップショットテスト(storyshot
)について触れたいと思います。