TypeScript で書いている React アプリで、ユニットテストも TypeScript で書くようにするための手順。
テスティングフレームワークはJest
で、React
コンポーネントのテストをしやすくするためにEnzyme
も使う。
また、アサーションライブラリとしてPower Assert
を使用する。
既に以下の記事の内容でTypeScript
が導入済みであるという前提で話を進める。
この記事で出てくるライブラリのバージョンは以下。
- jest@24.8.0
- babel-jest@24.8.0
- enzyme@3.9.0
- enzyme-adapter-react-16@1.13.2
- react-test-renderer@16.8.6
- power-assert@1.6.1
- babel-preset-power-assert@3.0.0
- typescript@3.4.5
- ts-jest@24.0.2
- @types/jest@24.0.13
- @types/enzyme@3.9.3
- eslint@5.16.0
- @typescript-eslint/parser@1.9.0
- @typescript-eslint/eslint-plugin@1.9.0
テスト対象のコンポーネントを作る
props
としてtext
を受け取り、その値を表示するコンポーネント。text
の型はstring
に指定している。
import React from 'react'; type Props = { text: string; }; const App: React.FC<Props> = ({text}) => <>{`Props is ${text}.`}</>; export default App;
<App text="foo" />
とすると、Props is foo.
と表示される。このコンポーネントをテストする。
JavaScript でテストを書く
説明を分かりやすくため、段階的に進めていく。まずは JavaScript でテストを書き、それを TypeScript に置き換えることにする。
必要なライブラリをインストールする。
$ yarn add -D jest babel-jest enzyme enzyme-adapter-react-16 react-test-renderer
Enzyme
はv3
からアダプタが必要になり、そのための設定の記述も必須になった。
アダプタのインストールは上記で済ませているので(enzyme-adapter-react-16
)、次は設定の記述。
テストの設定ファイルとしてルートディレクトリにtest.config.js
を作り、以下のように記述する。
これでアダプタが有効になる。
import Enzyme from 'enzyme'; import Adapter from 'enzyme-adapter-react-16'; Enzyme.configure({adapter: new Adapter()});
そしてJest
の設定ファイルとしてルートディレクトリにjest.config.js
を作り、テスト実行時にtest.config.js
を読み込むようにする。
module.exports = { setupFilesAfterEnv: ['<rootDir>/test.config.js'], };
これで設定は完了。あとはApp.test.js
として以下のテストコードを書いて$ yarn jest
を実行すると、テストが実行されて無事にパスする。
import React from 'react'; import {shallow} from 'enzyme'; import assert from 'assert'; import App from '../App'; describe('App', () => { it('表示する文字列に props.text を含む', () => { const wrapper = shallow(<App text="hogehoge" />); assert(wrapper.text().includes('hogehoge')); }); });
Power Assert の導入
Power Assert
を導入しないならここは飛ばしてもよい。
エラーメッセージが分かりやすくなるため個人的には必ず入れているが。
必要なライブラリは以下の2つ。
$ yarn add -D power-assert babel-preset-power-assert
babel.config.js
に以下の内容を追加して、テスト実行時にのみbabel-preset-power-assert
が有効になるようにしている。
if (process.env.NODE_ENV === 'test') { presets.push(['power-assert']); }
これで導入完了。試しにわざとテストを失敗させると、次のように出力される。
● App › 表示する文字列に props.text を含む assert.equal(received, expected) or assert(received) Expected value to be equal to: true Received: false Message: # src/__tests__/App.test.js:10 assert(wrapper.text().includes('foo')) | | | | | false | "Props is hogehoge." ShallowWrapper{} 8 | it('表示する文字列に props.text を含む', () => { 9 | const wrapper = shallow(<App text="hogehoge" />); > 10 | assert(wrapper.text().includes('foo')); | ^ 11 | }); 12 | }); 13 |
これで JavaScript ファイルとしてはテストコードを書けたので、次はこのコードを TypeScript に置き換える。
TypeScript でテストコードを書き、ユニットテスト時に型チェックも行うようにする
テストファイルの拡張子を.tsx
にする。
$ git mv src/__tests__/App.test.js src/__tests__/App.test.tsx
次に、TypeScript をトランスパイルするためにts-jest
を導入する。
インストールして、設定を記述する。
$ yarn add -D ts-jest
設定については、公式サイトが分かりやすく説明している。
JavaScript ファイルについてはbabel-jest
でトランスパイルさせたいので、preset
をts-jest/presets/js-with-babel
で指定する。
ts-jest/presets/js-with-babel TypeScript files will be handled by ts-jest, and JavaScript files will be handled by babel-jest.
記述場所はjest.config.js
。
diff --git a/jest.config.js b/jest.config.js index f3b8536..5f1bb3f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,3 +1,4 @@ module.exports = { setupFilesAfterEnv: ['<rootDir>/test.config.js'], + preset: 'ts-jest/presets/js-with-babel', };
最後に、jest
とenzyme
の型宣言ファイルをインストールする。
$ yarn add -D @types/jest @types/enzyme
これで完了。
確認のため、わざとtext
にnumber
を渡してテストしてみる。
diff --git a/src/__tests__/App.test.tsx b/src/__tests__/App.test.tsx index 52dd85b..acd1b3b 100644 --- a/src/__tests__/App.test.tsx +++ b/src/__tests__/App.test.tsx @@ -6,7 +6,7 @@ import App from '../App'; describe('App', () => { it('表示する文字列に props.text を含む', () => { - const wrapper = shallow(<App text="hogehoge" />); - assert(wrapper.text().includes('hogehoge')); + const wrapper = shallow(<App text={1} />); + assert(wrapper.text().includes('1')); }); });
assert
の条件は満たしているが型が違うので、エラーを出してくれる。
src/__tests__/App.test.tsx:9:34 - error TS2322: Type 'number' is not assignable to type 'string'.
ESLint の誤検知に対応する
張り切ってテストコードに型を書いていくと、ある問題が発生する。
diff --git a/src/__tests__/App.test.tsx b/src/__tests__/App.test.tsx index 52dd85b..2564664 100644 --- a/src/__tests__/App.test.tsx +++ b/src/__tests__/App.test.tsx @@ -1,12 +1,12 @@ import React from 'react'; -import {shallow} from 'enzyme'; +import {shallow, ShallowWrapper} from 'enzyme'; import assert from 'assert'; import App from '../App'; describe('App', () => { it('表示する文字列に props.text を含む', () => { - const wrapper = shallow(<App text="hogehoge" />); + const wrapper: ShallowWrapper = shallow(<App text="hogehoge" />); assert(wrapper.text().includes('hogehoge')); }); });
wrapper
に対してShallowWrapper
という型をつけたが、そのことで ESLint がエラーを出すようになってしまった。
no-unused-vars
が誤検知してしまっている。
2:18 error 'ShallowWrapper' is defined but never used no-unused-vars
いずれライブラリ側でこの問題が修正されるかもしれないが、この誤検知を消すのはすぐに出来るので対応する。
なお、ESLint の TypeScript 対応の基本的な設定はこちらを参照。
@typescript-eslint/eslint-plugin
が必要になるのでインストールする。
$ yarn add -D @typescript-eslint/eslint-plugin
.eslintrc
を編集。
先程インストールしたプラグインを追加して、no-unused-vars
のルールを一度無効にして、改めて有効にしている。
diff --git a/.eslintrc b/.eslintrc index 09486d6..33a6430 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,6 +1,9 @@ { "extends": "numb", "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint" + ], "settings": { "import/resolver": { "node": { @@ -13,5 +16,9 @@ ] } } + }, + "rules": { + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": "error" } }
これで直る。
以下の記事を参考にした。
Babel plugins を有効にする
Power Assert
を導入している場合は、まだ問題がある。
テストを失敗させると分かるが、Power Assert
が有効になっていない。
これは、Babel plugins が有効になっていないため。
設定で有効に出来るので、対応する。
これについても、公式サイトの説明が分かりやすい。
diff --git a/jest.config.js b/jest.config.js index 5f1bb3f..836c286 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,4 +1,9 @@ module.exports = { setupFilesAfterEnv: ['<rootDir>/test.config.js'], preset: 'ts-jest/presets/js-with-babel', + globals: { + 'ts-jest': { + babelConfig: true, + }, + }, };