30歳からのプログラミング

30歳無職から独学でプログラミングを開始した人間の記録。

Jest + Enzyme のテストコードを TypeScript で書く

TypeScript で書いている React アプリで、ユニットテストも TypeScript で書くようにするための手順。

テスティングフレームワークはJestで、ReactコンポーネントのテストをしやすくするためにEnzymeも使う。
また、アサーションライブラリとしてPower Assertを使用する。

既に以下の記事の内容でTypeScriptが導入済みであるという前提で話を進める。

numb86-tech.hatenablog.com

この記事で出てくるライブラリのバージョンは以下。

  • 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

Enzymev3からアダプタが必要になり、そのための設定の記述も必須になった。
アダプタのインストールは上記で済ませているので(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

設定については、公式サイトが分かりやすく説明している。

kulshekhar.github.io

JavaScript ファイルについてはbabel-jestでトランスパイルさせたいので、presetts-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',
 };

最後に、jestenzymeの型宣言ファイルをインストールする。

$ yarn add -D @types/jest @types/enzyme

これで完了。

確認のため、わざとtextnumberを渡してテストしてみる。

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 対応の基本的な設定はこちらを参照。

numb86-tech.hatenablog.com

@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"
   }
 }

これで直る。

以下の記事を参考にした。

teppeis.hatenablog.com

Babel plugins を有効にする

Power Assertを導入している場合は、まだ問題がある。

テストを失敗させると分かるが、Power Assertが有効になっていない。

これは、Babel plugins が有効になっていないため。
設定で有効に出来るので、対応する。
これについても、公式サイトの説明が分かりやすい。

kulshekhar.github.io

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,
+    },
+  },
 };

参考資料