はじめに
今回、Rails+Vueで作っているWebアプリに、Vueの自動テストを導入することになり、試行錯誤しました。
せっかくなので、備忘録代わりに置いておきます。
「Vue 自動テスト」で検索すると、TypescriptやらVue-cliやらの話が結構引っかかりますが、今回は両方とも使っていません。
環境
rails 7.0.2.3
vue.js 3.2.37
参考ドキュメント
バージョンは古いですが、結局本家のドキュメントに一番助けてもらいました。
https://v1.test-utils.vuejs.org/ja/
ちなみに、新しいバージョンのドキュメントは2022.11.21現在、なぜか閲覧できません。。。
使うテストツール
vue-test-utils
Vue標準のテストライブラリです。
Jest
vue-test-utils
で書いたテストを実行するテストランナです。
公式ドキュメントで一番上に書いてあったし、弊社ではフロントエンドテストの実績がなく、こだわりがないのでJestを選びました。
Jest以外のランナを使っても基本的には動かせるみたいです。
Vue.jsの自動テスト
セットアップ
基本的には、公式ドキュメントを読みながらひたすら必要なものをインストールしていきます。
ドキュメントはnpm
コマンドで書いてありますが、今回は開発環境の兼ね合いで、yarn
コマンドを使ってインストールしていきました。
仮想ブラウザ環境のインストール
ライブラリがブラウザ環境に依存するので、テストを仮想ブラウザ環境で実行できるようにするものです。
yarn add jsdom jsdom-global --dev
vue-test-utilsとJestのインストール
yarn add jest @vue/test-utils --dev
vue-jestのインストール・設定
.vue
ファイルをJestに扱ってもらうために、こちらもインストールします。
公式にはvue-jest
をインストールしてと書いてありますが、今回はvue3用のテストを書きたいので、@vue/vue3-jest
をインストールします。
yarn add @vue/vue3-jest --dev
package.json
ファイルをルートディレクトリに作り、jestブロックを作って設定を書き入れます。
{
"jest": {
"moduleFileExtensions": [
"js",
"json",
// *.vue ファイルを処理するように指示する
"vue"
],
"transform": {
// vue-jest で *.vue ファイルを処理する
".*\\.(vue)$": "vue-jest"
}
}
}
ちなみにjestの設定は後々膨らんでいくので、それが気になる場合はpackage.json
にjestブロックを作るのをやめ、jest.config.js
ファイルをルートディレクトリに作るのもおすすめです。私は今回、こちらの方法を取りました。
module.exports = {
"moduleFileExtensions": [
"js",
"json",
// *.vue ファイルを処理するように指示する
"vue"
],
"transform": {
// vue3-jest で *.vue ファイルを処理する
".*\\.(vue)$": "@vue/vue3-jest"
}
}
babel-jestのインストール・設定
ES-modules構文などを使うために、babel-jest
もインストールします。
yarn add babel-jest --dev
インストール後、jsのテストファイルをbabelで処理できるように、以下の設定をjest.config.js
(またはpackage.json
)に追記します。
module.exports = {
// ...
"transform": {
'^.+\\.js$' : '<rootDir>/node_modules/babel-jest',
},
}
自動テストのディレクトリ構成
Jestの公式ドキュメントは、テスト対象のすぐ隣に__tests__
ディレクトリを作ってテストを書くことを推奨していますが、強制ではないようです。
今回はRails+Vueのアプリに自動テストを導入するので、Railsがデフォルトで作るtest
ディレクトリにjavascript/__tests__
ディレクトリを作り、そこにテストを書いていくことにしました。
カバレッジの設定
カバレッジを収集するための設定を書きます。
ここをちゃんと設定しないと、1ケースだけの自動テストに1分以上もかかることになるので、ちゃんとやります。
まずは、カバレッジを収集するために、下の設定を追記します。
module.exports = {
// ...
"collectCoverage": true,
"coverageReporters": ["html", "text-summary"],
"collectCoverageFrom": [
"app/javascript/**/*.{js,vue}"
]
}
collectCoverageFrom
オプションで、どのディレクトリのファイルのカバレッジを収集するかを設定しました。
今回は、app/javascript
ディレクトリの下だけ収集できればいいのでそう書いていますが、公式のように"**/*.{js,vue}"
と書けば、アプリ全体から収集してくれます。
次に下を追記します。
module.exports = {
// ...
"coverageDirectory": "coverage/_jest"
}
カバレッジのディレクトリ構成を指定するオプションです。今回は、Railsのcoverage
ディレクトリの下に_jes
tディレクトリを置き、そこにカバレッジを収集することにしました。
自動テストを書く
テストを試験的に実行するために、コンポーネントの存在確認をするだけの簡単な自動テストを書いてみます。
import { mount } from '@vue/test-utils';
import Component from '(省略)/component';
const wrapper = mount(Component);
test('コンポーネントを描画できる', () => {
// Componentを描画できる
const comp = wrapper.findComponent(Component);
expect(comp.exists()).toBe(true);
}
自動テストを実行
あとは実行するだけです。お疲れ様でした!
yarn test
テスト実行時に遭遇したエラーまとめ
ここからは、テストを実行するときに遭遇したエラーと、その解決法をまとめていきます。
テストの実行コマンド違い
一番しょぼい間違いですが、テストの実行コマンドを勘違いしており、
yarn test
ではなく
yarn jest
で実行していました。
SyntaxError: Cannot use import statement outside a module
原因はいろいろありましたが、まずはpackage.json
に下を追記しました。
{
"scripts": {
// ...
"test": "jest"
}
}
それでもうまくいかなく原因を探した結果、jestのtransform
オプションの中身を書き違えていることに気づきました。
module.exports = {
// ....
"transform": {
".*\\.(vue)$": "vue-jest", // 間違っている方
".*\\.(vue)$": "@vue/vue3-jest" // 正しい方
}
}
ドキュメントが古いため勘違いしていましたが、今回はvue3に合わせたプリプロセッサをインストールしているので、こちらもそれに合わせた書き方をしないといけません。
この2つを直した結果、無事にこのエラーは乗り越えました。
Error: Cannot find module '@vue/compiler-sfc’
@vue/compiler-sfc
が必要らしいのでインストールして終わりです。
yarn --cwd /ec-web add @vue/compiler-sfc --dev
ReferenceError: document is not defined
jest-environment-jsdom
をインストール・設定していないために起きるエラーみたいです。
まずはインストールします。
yarn --cwd /ec-web add jest-environment-jsdom --dev
次に、テストファイルの一番上に以下を追記します。
/**
* @jest-environment jsdom
*/
// ↑↑↑追記↑↑↑
import { mount } from '@vue/test-utils';
import Component from '(省略)/component';
// ...
ReferenceError: Vue is not defined
おそらく、vue3、jest v.28以上で起きるエラーのようです。jest.config.js
に以下を追記します。
module.exports = {
// ...
"testEnvironmentOptions": {
"customExportConditions": ["node", "node-addons"]
}
}
テストを書くときに気をつけたかったエラー
上のエラーたちとは毛色が違うけれど、乗り越えるのに時間がかかったエラーも紹介します。
Cannot find module 'Component' from '(ファイルパス)'
テストファイルでimportするときに、パスが正しくなくて遭遇したエラーです。
正しいパスで書くのが事情があってどうしても難しく、解決法としてaliasを設定しにいきました。
module.exports = {
// ...
"moduleNameMapper": {
"^controllers(.*)$": "<rootDir>/(ファイルパス)"
}
}
isVueInstance is not a function
テストを実行できるか試しているときに、最初に試験的に書いたテストです。
const wrapper = mount(Component)
expect(wrapper.isVueInstance()).toBe(true)
これのisVueInstance
が使えないというエラーですが、公式ドキュメントに思いっきり
isVueInstance は非推奨となり、将来のリリースで削除される予定です。
と書いてありました。
今回はバージョンが上がった方を使っているので、もう削除されて使えなくなったんだと思います。
provide/injectのスタブ
provide/injectをスタブしたくて、公式ドキュメント通りに
const wrapper = mount(Component, {
provide: {
foo() {}
}
}
と書いたら、this.foo() is not a function
エラーが出ました。
調べたところ、Vue3用では書き方が変わっているようで、正しくは下のように書くといいようです。
const wrapper = mount(Component, {
global: {
provide: {
foo() {}
}
}
}
mountをする場所
最初はtestの中でmount
をしていましたが、どうもテストのメソッドが思うように動いてくれませんでした。
test 'componentを描画できる', () => {
const wrapper = mount(Component);
// ...
}
結果として、mount
やshallowMount
はtestの外でするといいみたいです。理由はよくわかりませんでした。
const wrapper = mount(Component);
test 'componentを描画できる', () => {
// ...
}
おわりに
今回、初めて開発環境にフロントエンドテストを入れましたが、私自身が環境系に疎い上に社内に詳しい人がいなかったので、結構な時間がかかりました。
試行錯誤する時間は楽しかったし、良い経験になったと思います。
ところで、時間がかかった3分の1くらいは、バージョンアップ関連のエラーでした。
公式ドキュメント、早く正常にみられるようになると嬉しいです。。。