【Vue】Vue x TypeScript x Vuetify プロジェクトのテスト環境(Jest)を整える
Vue公式のやり方で、TypeScriptに対応した後Vuetifyを追加するとエラーが出て長時間詰まったので別のやり方を試すことに。。
手順
vue create my-sample-proj
でプロジェクトを作成する(この際にTypeScript
,Unit Testing
を有効にしておく)vue add vuetify
でVuetifyを導入- Vuetifyを使ったコンポーネントを作成
- Vuetifyコンポーネントをテストする際の設定
- テストコードを書いてみる
1. TypeScriptとUnit Testingを導入したプロジェクトの作成
まずはvue create
を実行。
? Please pick a preset:
ではManually select features
を選択。
? Check the features needed for your project:
では、Choose Vue version
, Babel
,TypeScript
,Unit Testing
にチェックを入れる。(ほかはお好みでOKと思われる)
他の設定は基本デフォルトで、? Pick a unit testing solution:
ではJest
を選択する。
vue create vue-test-sample-ts Vue CLI v4.5.13 ? Please pick a preset: Manually select features ? Check the features needed for your project: Choose Vue version, Babel, TS, Linter, Unit ? Choose a version of Vue.js that you want to start the project with 2.x ? 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: Prettier ? Pick additional lint features: Lint on save ? Pick a unit testing solution: Jest ? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files ? Save this as a preset for future projects? No
確認
vue create
が完了すると、tests/unit
にexample.spec.ts
が作成されており、最初からTypeScriptに対応していることがわかる。
npm run test:unit
でテストを実行すると、1つだけあるテストが成功するはず。
npm run test:unit PASS tests/unit/example.spec.ts HelloWorld.vue ✓ renders props.msg when passed (20ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 3.271s Ran all test suites.
2. プロジェクトにVuetifyを追加する
vue add vuetify
を実行するとHelloWorld.vue
が書き換えられてしまうが(←example.spec.ts
が動かなくなる)、実行後にHelloWorld.vue
の変更を取り消しすればOK。
念の為、ここでもnpm run test:unit
を実行しておく。
npm run test:unit PASS tests/unit/example.spec.ts HelloWorld.vue ✓ renders props.msg when passed (17ms) Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total Snapshots: 0 total Time: 1.571s, estimated 3s Ran all test suites.
3. Vuetifyコンポーネントを作成する
Vuetifyのサンプルに掲載してある、ボタンを押すと開閉するカードを使う。
<template> <v-card class="mx-auto ma-4" max-width="344"> <v-img src="https://cdn.vuetifyjs.com/images/cards/sunshine.jpg" height="200px" ></v-img> <v-card-title> Top western road trips </v-card-title> <v-card-subtitle> 1,000 miles of wonder </v-card-subtitle> <v-card-actions> <v-btn color="orange lighten-2" text> Explore </v-btn> <v-spacer></v-spacer> <v-btn icon @click="show = !show"> <v-icon>{{ show ? "mdi-chevron-up" : "mdi-chevron-down" }}</v-icon> </v-btn> </v-card-actions> <v-expand-transition> <div v-show="show"> <v-divider></v-divider> <v-card-text> I'm a thing. But, like most politicians, he promised more than he could deliver. You won't have time for sleeping, soldier, not with all the bed making you'll be doing. Then we'll go with that data file! Hey, you add a one and two zeros to that or we walk! You're going to do his laundry? I've got to find a way to escape. </v-card-text> </div> </v-expand-transition> </v-card> </template> <script lang="ts"> import { Component, Vue } from "vue-property-decorator"; @Component export default class CustomCard extends Vue { show = false; } </script>
See the Pen Vuetify Example Pen by popy1017 (@popy1017) on CodePen.
仕様としては、左下の「EXPLORE」ボタンを押しても何も起きないが、右下のアイコンボタンを押すと詳細情報が表示されるというもの。
4. Vuetifyコンポーネントをテストする際の設定
この設定を行わないとv-card
などのVuetify固有のタグが認識されずエラーや警告が出てしまう。
[Vue warn]: Unknown custom element: <v-card> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
tests/
配下に以下のsetup.ts
を用意する。
import Vue from "vue"; import Vuetify from "vuetify"; Vue.use(Vuetify);
さらに、jest.config.js
に以下を追加する。
module.exports = { ~~ setupFiles: ["./tests/setup.ts"], };
テストファイルが少ない場合などは、setup.ts
の内容をspec.ts
ファイルに直書きでもOK。
参考記事
5. テストコードを書く
今回は以下の観点でテストコードを作成する。
- 画面描画時に詳細(
v-card-text
)は非表示になっている - 右下のアイコンボタンをクリックすると、詳細が表示される
注意点
あまりよくわかっていないが、
テスト対象のコンポーネントをmount
する際は以下のようにする必要がある。
import { createLocalVue, mount, shallowMount } from "@vue/test-utils"; import CustomCard from "@/components/CustomCard.vue"; import Vuetify from "vuetify"; describe("CustomCard.vue", () => { const localVue = createLocalVue(); let vuetify: Vuetify; beforeEach(() => { vuetify = new Vuetify(); }); it("should hide details when rendered", async () => { // マウントするときに localVue, vuetify を一緒に渡す。 const wrapper = mount(CustomCard, { localVue, vuetify });
shallowMount
の際はなくても問題ないと思われる。
作成したテストコード
import { createLocalVue, mount, shallowMount } from "@vue/test-utils"; import CustomCard from "@/components/CustomCard.vue"; import Vuetify from "vuetify"; describe("CustomCard.vue", () => { const localVue = createLocalVue(); let vuetify: Vuetify; beforeEach(() => { vuetify = new Vuetify(); }); it("should have data named 'show' with default false when rendered", () => { const wrapper = shallowMount(CustomCard); expect(wrapper.vm.$data.show).toBe(false); }); it("should hide details when rendered", async () => { const wrapper = mount(CustomCard, { localVue, vuetify }); const details = wrapper.find(".v-card__text"); expect(details.isVisible()).toBe(false); }); it("should show details when icon button clicked", async () => { const wrapper = mount(CustomCard, { localVue, vuetify }); const details = wrapper.find(".v-card__text"); const button = wrapper.find(".v-btn--icon"); await button.trigger("click"); expect(details.isVisible()).toBe(true); }); });
表示・非表示の切り替え変数の値をチェックする
wrapper.vm.$data.{変数名}
で値をチェックできる。
it("should have data named 'show' with default false when rendered", () => { const wrapper = shallowMount(CustomCard); expect(wrapper.vm.$data.show).toBe(false); });
v-card-textが表示されているかどうかをチェックする
要素.isVisible()
で確認できる。
style が display: none か visibility: hidden の親要素がある場合、 false を返します。 コンポーネントが v-show によって非表示になっているかアサートすることに使用することができます。 Wrapper | Vue Test Utils
it("should hide details when rendered", async () => { const wrapper = mount(CustomCard, { localVue, vuetify }); const details = wrapper.find(".v-card__text"); expect(details.isVisible()).toBe(false); });
ボタンをクリックする
要素.trigger(イベント名)
(クリックイベントの場合は"click"
)でOK。
イベントの完了を待つためにawait
を使っている。
it("should show details when icon button clicked", async () => { const wrapper = mount(CustomCard, { localVue, vuetify }); const details = wrapper.find(".v-card__text"); const button = wrapper.find(".v-btn--icon"); await button.trigger("click"); // <= ここ expect(details.isVisible()).toBe(true); });