WebアプリケーションのフロントエンドフレームワークとしてVue3をよく使用しています。
ただ、テストコードまでは作成できておらず、コードの品質を担保することが出来ていませんでした。
今回は、公式の Vue Test Utils にあるチュートリアルを実践してみます。
テストコードを書くより、テストを走らせるまでの準備が大変でした。
A Crash Course | Vue Test Utils (vuejs.org)
実行環境
$ node -v v18.14.0 $ npm --version 9.4.2
プロジェクト作成
vite を使用し、Vue3のプロジェクトを作成します。
# プロジェクト作成 $ npm create vite@latest ✔ Project name: … vue-test-project ✔ Select a framework: › Vue ✔ Select a variant: › JavaScript # 開発サーバー起動 $ cd vue-test-project $ npm install $ npm run dev > vue-test-project@0.0.0 dev > vite VITE v4.1.2 ready in 185 ms ➜ Local: http://localhost:5173/ ➜ Network: use --host to expose ➜ press h to show help
起動し、指定されたURLにアクセスします。
テスト用ライブラリの追加
Vueコンポーネントテスト
まずは、公式のVue Test Utils を参考にインストールします。
Installation | Vue Test Utils (vuejs.org)
$ npm install --save-dev @vue/test-utils
テストランナーで推奨されているjest
をインストール
次にインストールするvue-jest
に合わせるため、バージョンを固定します。
$ npm install --save-dev jest@28.1.3
jest
でVueコンポーネント(.vue)をテストする場合、vue-jest
というtransformerが必要という事でした。調べてもわからなかったのですが、おそらくテスト用にファイルを変換してくれるものだと思います。
こちらはVue
とjest
のバージョンによって、パッケージとバージョンが決まっているようでした。今回はVue3 , jest@28.x なので@vue/vue3-jest@28.x
をインストールしています。
GitHub - vuejs/vue-jest: Jest Vue transformer
$ npm install --save-dev @vue/vue3-jest@28
次に、テストに使用するテスト環境のためののライブラリです。
一度テストを実行したときのエラーにかかれてあったため、追加でインストールしました。
デフォルトではnode
の環境を使用するが、コンポーネントのテストをする場合はjsdom
が必要なようです。
npm install --save-dev jest-environment-jsdom
ここでVueコンポーネントのテストに必要なライブラリは揃いました。
以下は不要なのですが、JavaScriptコードをテストするための準備になります。
JavaScriptテスト
jestでES6(ES2015)で書かれたJavaScriptコードをテストするためには、BabelをCommonJSに変換が必要だそうです。この変換を行うためbabel-jest
をインストールします。@babel/core
はbabel-jestを使用するためにインストールしています。
$ npm install --save-dev babel-jest@28 $ npm install --save-dev @babel/core
babelのプラグインでコードの変換する際のターゲット環境を設定できます。この環境のプリセットを設定できるライブライをインストールします。
npm install --save-dev @babel/preset-env
Jestの設定
package.jsonにscriptsでテスト実行コマンドを追加します。 今回は/tests
ディレクトリ以下にテストコードを作成していくため、jest tests
としています。
{ "scripts": { "test:unit": "jest tests" } }
他の設定もpackage.jsonにまとめて追加します。jest.config.jsonという名前で別ファイルを準備してもいいみたいです。
- testEnvironment: テスト環境
- testEnvironmentOptions: テスト環境オプション
- moduleFileExtensions: モジュールが使用するファイル拡張子
- transform: トランスフォーマーが変換するファイルの指定
- moduleNameMapper: 正規表現でimportされたモジュール名にマッチするものを置き換え
import Component from ../../src/components/HelloWorld
と書いていたがsrc/
までのパスを@/
で書くことが出来るようになります。
{ "jest": { "testEnvironment": "jsdom", "testEnvironmentOptions": { "customExportConditions": [ "node", "node-addons" ] }, "moduleFileExtensions": [ "js", "json", "vue" ], "transform": { ".*\\.(js)$": "babel-jest", ".*\\.(vue)$": "@vue/vue3-jest" }, "moduleNameMapper": { "^@/(.*)$": "<rootDir>/src/$1" } } }
Babelの設定
babelの設定もpackage.jsonに書いていきます。babel.config.jsという名前で別ファイルを準備してもいいみたいです。
- presets: プリセットとして利用するプラグインのリスト
- node: current とすることで、現在のnode.jsに合わせたコードに変換をしてくれる
{ "babel": { "presets": [ [ "@babel/preset-env", { "targets": { "node": "current" } } ] ] } }
これでやっとテストの準備ができました。(実際は何度もテスト実行し、足りていないライブラリや、設定を追加していきました。)
テストコード作成
ここからはチュートリアルに従って、テストコードを書いていきます。Options API
のスタイルで書かれていたが、新しいComposition APIのスタイルに変更しています。
本来はテストコードを作成し、そのテストが通るように修正していくのですが、こちらは全ての修正が完了した状態になります。
テスト対象コンポーネント
/src/components/TodoApp.vue
<script setup> import { ref } from "vue" const todos = ref([ { id: 1, text: "Learn Vue.js 3", completed: false } ]); const newTodo = ref(""); const createTodo = () => { todos.value.push( { id: 2, text: newTodo, completed: false } ); }; </script> <template> <div> <div v-for="todo in todos" :key="todo.id" data-test="todo" :class="[todo.completed ? 'completed' : '']"> {{ todo.text }} <input type="checkbox" v-model="todo.completed" data-test="todo-checkbox" /> </div> <form data-test="form" @submit.prevent="createTodo"> <input data-test="new-todo" v-model="newTodo" /> <input type="submit"> </form> </div> </template>
テストコード
/tests/unit/TodoApp.spec.js
import { mount } from "@vue/test-utils" import TodoApp from "@/components/TodoApp" test("renders a todo", () => { const wrapper = mount(TodoApp); const todo = wrapper.get("[data-test='todo']"); expect(todo.text()).toBe("Learn Vue.js 3"); }) test("creates a todo", async () => { const wrapper = mount(TodoApp); await wrapper.get("[data-test='new-todo']").setValue("New todo"); await wrapper.get("[data-test='form']").trigger("submit"); expect(wrapper.findAll("[data-test='todo']")).toHaveLength(2); }) test("completes a todo", async () => { const wrapper = mount(TodoApp); await wrapper.get("[data-test='todo-checkbox']").setValue(true); expect(wrapper.get("[data-test='todo']").classes()).toContain("completed"); })
テスト実行
$ npm run test:unit > vue-test-project@0.0.0 test:unit > jest tests PASS tests/unit/TodoApp.spec.js ✓ renders a todo (21 ms) ✓ creates a todo (8 ms) ✓ completes a todo (5 ms) Test Suites: 1 passed, 1 total Tests: 3 passed, 3 total Snapshots: 0 total Time: 0.887 s, estimated 1 s Ran all test suites matching /tests/i.
カバレッジ出力
簡単な見方としては
- Stmts: C0 実行された行の網羅率
- Branch: C1 ifなのど分岐された処理の網羅率
- Funcs: 各関数呼び出し網羅率
- Lines: ファイルの各実行可能行が実行されたかの網羅率。Stmts と同じ?
$ npm run test:unit -- --coverage > vue-test-project@0.0.0 test:unit > jest tests --coverage [vue-jest]: Not found tsconfig.json. PASS tests/unit/TodoApp.spec.js ✓ renders a todo (22 ms) ✓ creates a todo (8 ms) ✓ completes a todo (4 ms) -------------|---------|----------|---------|---------|------------------- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s -------------|---------|----------|---------|---------|------------------- All files | 100 | 100 | 100 | 100 | TodoApp.vue | 100 | 100 | 100 | 100 | -------------|---------|----------|---------|---------|------------------- Test Suites: 1 passed, 1 total Tests: 3 passed, 3 total Snapshots: 0 total Time: 0.903 s, estimated 1 s Ran all test suites matching /tests/i.
/coverage/lcov-report/index.html
にレポートも出力されるようです。便利。