Vue.js + TypeScriptのプロジェクトにJestを入れて、Vueコンポーネントのメソッドのテストをしたかったのですが、思いのほか詰まりまくったので記事にしておきます。
Vue Test Utilsという公式パッケージを使ってVue.jsとJestを連携させるのですが、Babel 7の変更にJestやvue-test-utilsが追いついていない感じがあって、ドキュメント通りにやっても上手くいきませんでした。Jestのために他のパッケージのバージョンを落とすのも嫌だったので、解決策を調べてみました。
サンプルコードはこちらです。
https://github.com/kecbigmt/vue-test-utils-ts-sample
筆者の環境
- OS: MacOS High Sierra 10.13.6
- node: 10.16.3
- vue-cli: 3.11.0
- yarn: 1.19.0
手順
以下の流れで説明していきます。
- プロジェクト作成
- 設定ファイルの編集
- テストコードの追加
- テスト実行
プロジェクト作成
vue-cliでプロジェクトを作成します。手動でTSをONにします。あとはお好みで。
$ vue create vue-test-utils-ts-sample
Vue CLI v3.11.0
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, TS, Linter
? Use class-style component syntax? Yes
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes
? Pick a linter / formatter config: TSLint
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)Lint on save
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No
パッケージのインストール
必要なパッケージをインストールします。計8個のパッケージをまとめて入れています。
$ yarn add --dev jest @types/jest @vue/test-utils vue-jest babel-jest ts-jest @babel/core babel-core@^7.0.0-bridge.0 @babel/preset-env
# npmの場合は`npm install -D ...`
以下、備忘録のためにも、何を何のためにインストールしたのかを解説します。
(最初の3つについては省略)
xxx-jest
$ yarn add --dev vue-jest babel-jest ts-jest
-
vue-jest
: .vueのファイルをJestに読ませるために必要 -
babel-jest
: ES2015のような新しめの文法で書いたスクリプトをJestに読ませるために必要 -
ts-jest
: .tsのファイルをJestに(ry
@babel/core & babel-core
$ yarn add --dev @babel/core babel-core@^7.0.0-bridge.0
babel-jest
の依存関係のなかにbabel-core
が入っていています。しかし、Babel 7以降、babel-coreの最新バージョンは@babel/core
として管理されています。
とはいえ、そのままだとbabel-jestが新しい名前を認識してくれないので、babel-jestに@babel/coreを渡すための繋ぎとして、babel-core@^7.0.0-bridge.0
も一緒に入れます。
参考: babel/babel-bridge: A placeholder package that bridges babel-core to @babel/core.
@babel/preset-env
$ yarn add --dev @babel/preset-env
vue-cliで作成したプロジェクトでは、babelによるコンパイルを行うときのプリセットとして@vue/app
というのを使ってるみたいですが、Jestが.vueファイルを解釈しようとするときに上手くいかないようです。
そのため、Babelの一般的なプリセットである@babel/preset-env
をテストのときだけ使います。プリセットの切り替えについて設定ファイルに記述する部分は後ほど紹介します。
ちなみに、筆者の環境では@babel/preset-envを使わないとこんなエラーが出ました。
$ yarn run test:unit
# 中略
/path/to/vue-test-utils-ts-sample/node_modules/@babel/runtime-corejs2/helpers/esm/classCallCheck.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){export default function _classCallCheck(instance, Constructor) {
^^^^^^
SyntaxError: Unexpected token export
設定ファイルの編集
パッケージのインストールが終わったら、設定ファイルを編集していきます。編集するファイルはpackage.json
・babel.config.js
・tsconfig.json
の3つです。
package.json
以下のように記述を追加します。テスト用のコマンドを追加したり、どのファイルのときに何を使って解釈するのかを設定したりしてます。
{
"scripts": {
"test:unit": "jest"
}
// ...
"jest": {
"moduleFileExtensions": [
"js",
"ts",
"json",
"vue"
],
"transform": {
".*\\.(vue)$": "vue-jest",
"^.+\\.js$": "<rootDir>/node_modules/babel-jest",
"^.+\\.tsx?$": "ts-jest"
},
"transformIgnorePatterns": [
"/node_modules/(?!@babel/runtime-corejs2)"
]
}
}
最後のtransformIgnorePatterns
の部分はエラーを回避するために入れました。Babel 7 + Jestの特有のエラーであるようですが、これがないと以下のように出てきます。
(@babel/preset-envが無いときと同じエラーです)
$ yarn run test:unit
# 中略
/path/to/vue-test-utils-ts-sample/node_modules/@babel/runtime-corejs2/helpers/esm/classCallCheck.js:1
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,global,jest){export default function _classCallCheck(instance, Constructor) {
^^^^^^
SyntaxError: Unexpected token export
参考: あーありがち - 2019-02時点でJestの設定で最後までハマったもの
babel.config.js
Babelのプリセットについて記述します。ここではテストのときだけ@babel/preset-envを使用するよう記述します。
module.exports = {
presets: [
'@vue/app'
],
env: {
test: {
presets: [['@babel/preset-env', { targets: { node: 'current' } }]]
}
}
}
参考: Testing Single-File Components with Jest | Vue Test Utils
tsconfig.js
最後にこのファイルです。テストコードのなかでJestの関数を使えるようにするために追加します。
{
"compilerOptions": {
"types": [
"jest",
//..
],
//..
},
//..
}
テストコードの追加
続いて、テストコードを追加します。ここでは、プロジェクト作成時に自動で生成されるsrc/component/HelloWorld.vue
のテストを追加します。
HelloWorld.vueと同じ階層に HelloWorld.spec.ts
というファイルを新しく作成します。
ファイルの中には以下のように記述します。
import { shallowMount } from '@vue/test-utils';
import HelloWorld from './HelloWorld.vue';
describe('HelloWorld.vue', () => {
test('renders props.msg when passed', () => {
const msg = 'new message';
const wrapper = shallowMount(HelloWorld, {
propsData: { msg },
});
expect(wrapper.text()).toMatch(msg);
});
});
(公式ドキュメントではテスト対象ファイルと同じ階層に__tests__
というディレクトリを作って、その中にテストファイルを格納することが推奨されています。ですが、自分なりの配置をしても構わないとも記載されています)
テストの内容を軽く説明します。
const msg = 'new message';
const wrapper = shallowMount(HelloWorld, {
propsData: { msg },
});
まず、msg
というpropに"new message"という値を渡した上で、HelloWorldコンポーネントをマウント・レンダリングします。
shallowMount()
は@vue/test-utilsの関数。浅くマウント(コンポーネントの中のコンポーネントまでは生成されない)され、レンダリングされたVueコンポーネントを含むWrapperを返します。
shallowMount()やその他の関数についてはAPI | Vue Test Utilsで説明されています。
expect(wrapper.text()).toMatch(msg);
WrapperはVueコンポーネントに関するいろいろな情報を返してくれます。wrapper.text()
はコンポーネントに含まれるDOM要素内の文字列(<p>この部分</p>
)をすべて繋げて返します。
JestのtoMatch()メソッドを使って、これに"new message"が含まれているかをテストしています。
WrapperについてはWrapper | Vue Test Utilsが参考になります。
テスト実行
用意が整ったので、いよいよテストを実行します。package.jsonで設定した通り、test:unit
のコマンドを使用できるようになっています。
$ yarn run test:unit # npmの場合は`npm run test:unit`
yarn run v1.19.0
$ jest
PASS src/components/HelloWorld.spec.ts
HelloWorld.vue
✓ renders props.msg when passed (19ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.842s
Ran all test suites.
✨ Done in 2.87s.
以上です。テストが動くようになりました。
サンプルコードはこちらです。
https://github.com/kecbigmt/vue-test-utils-ts-sample