🔥

【Rails7+Vue3】Vue.jsの自動テストを導入するまで

2022/12/23に公開

はじめに

今回、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ブロックを作って設定を書き入れます。

package.json
{
  "jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      // *.vue ファイルを処理するように指示する
      "vue"
    ],
    "transform": {
      // vue-jest で *.vue ファイルを処理する
      ".*\\.(vue)$": "vue-jest"
    }
  }
}

ちなみにjestの設定は後々膨らんでいくので、それが気になる場合はpackage.jsonにjestブロックを作るのをやめ、jest.config.jsファイルをルートディレクトリに作るのもおすすめです。私は今回、こちらの方法を取りました。

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)に追記します。

jest.config.js
module.exports = {
  // ...
  "transform": {
    '^.+\\.js$'  : '<rootDir>/node_modules/babel-jest',
  },
}

自動テストのディレクトリ構成

Jestの公式ドキュメントは、テスト対象のすぐ隣に__tests__ディレクトリを作ってテストを書くことを推奨していますが、強制ではないようです。
今回はRails+Vueのアプリに自動テストを導入するので、Railsがデフォルトで作るtestディレクトリにjavascript/__tests__ディレクトリを作り、そこにテストを書いていくことにしました。

カバレッジの設定

カバレッジを収集するための設定を書きます。
ここをちゃんと設定しないと、1ケースだけの自動テストに1分以上もかかることになるので、ちゃんとやります。

まずは、カバレッジを収集するために、下の設定を追記します。

jest.config.js
module.exports = {
  // ...
  "collectCoverage": true,
  "coverageReporters": ["html", "text-summary"],
  "collectCoverageFrom": [
    "app/javascript/**/*.{js,vue}"
  ]
}

collectCoverageFromオプションで、どのディレクトリのファイルのカバレッジを収集するかを設定しました。
今回は、app/javascriptディレクトリの下だけ収集できればいいのでそう書いていますが、公式のように"**/*.{js,vue}"と書けば、アプリ全体から収集してくれます。

次に下を追記します。

jest.config.js
module.exports = {
  // ...
  "coverageDirectory": "coverage/_jest"
}

カバレッジのディレクトリ構成を指定するオプションです。今回は、Railsのcoverageディレクトリの下に_jestディレクトリを置き、そこにカバレッジを収集することにしました。

自動テストを書く

テストを試験的に実行するために、コンポーネントの存在確認をするだけの簡単な自動テストを書いてみます。

test/javascript/__tests__/component.test.js
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に下を追記しました。

package.json
{
  "scripts": {
    // ...
    "test": "jest"
  }
}

それでもうまくいかなく原因を探した結果、jestのtransformオプションの中身を書き違えていることに気づきました。

jest.config.js
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

次に、テストファイルの一番上に以下を追記します。

test/javascript/__tests__/component.test.js
/**
 * @jest-environment jsdom
 */

// ↑↑↑追記↑↑↑
import { mount } from '@vue/test-utils';
import Component from '(省略)/component';
// ...

https://github.com/testing-library/react-testing-library/issues/422#issuecomment-737905314

ReferenceError: Vue is not defined

おそらく、vue3、jest v.28以上で起きるエラーのようです。jest.config.jsに以下を追記します。

jest.config.js
module.exports = {
  // ...
  "testEnvironmentOptions": {
    "customExportConditions": ["node", "node-addons"]
  }
}

https://stackoverflow.com/questions/72428323/jest-referenceerror-vue-is-not-defined

テストを書くときに気をつけたかったエラー

上のエラーたちとは毛色が違うけれど、乗り越えるのに時間がかかったエラーも紹介します。

Cannot find module 'Component' from '(ファイルパス)'

テストファイルでimportするときに、パスが正しくなくて遭遇したエラーです。
正しいパスで書くのが事情があってどうしても難しく、解決法としてaliasを設定しにいきました。

jest.config.js
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() {}
    }
  }
})

https://qiita.com/KazukiMiyazato/items/2ab3a96df5a509e46c6b

mountをする場所

最初はtestの中でmountをしていましたが、どうもテストのメソッドが思うように動いてくれませんでした。

test 'componentを描画できる', () => {
  const wrapper = mount(Component);
  // ...
}

結果として、mountshallowMountはtestの外でするといいみたいです。理由はよくわかりませんでした。

const wrapper = mount(Component);
test 'componentを描画できる', () => {
  // ...
}

おわりに

今回、初めて開発環境にフロントエンドテストを入れましたが、私自身が環境系に疎い上に社内に詳しい人がいなかったので、結構な時間がかかりました。
試行錯誤する時間は楽しかったし、良い経験になったと思います。

ところで、時間がかかった3分の1くらいは、バージョンアップ関連のエラーでした。
公式ドキュメント、早く正常にみられるようになると嬉しいです。。。

参照リンクまとめ

Discussion