Vue Test Utilsを使ったvue.jsの単一ファイルコンポーネントのテストに興味があったのですが、あまり書く機会がなかったので今回個人的に書いてみた感じのメモです。
はじめに
vueは2系、使用したテストランナーはjestです。
Vue Test Utils : テストランナを選ぶ
他、typeScriptを使用、またVuexの注入について軽く触れます。
また、セットアップの流れは公式に詳しいのですが、個人的にパッケージ周りとjestの設定について調べてみてわかったことを書きます。
セットアップ
パッケージに関しては、@vue/test-utils
をはじめ、jestに.vueファイルの処理方法を教えるために必要なvue.jest
や他babel-jest
ts-jest
jest-serializer-vue
などは、
既に@vue/cli-plugin-unit-jest が導入されていれば基本的なところは網羅されているため、特段事情がなければ、まずはこちらで事足りそうです。
こちらはvue-cli
からvue create project
する際に、
Check the features needed for your project
でUnit Testing
にチェック(Pick a unit testing solution: ではJestを選択)で初期導入が可能です。
念の為vue-cli
のバージョンは4系で現在最新の 4.5.11
です。
既存プロジェクトで初期に導入し損ねていた場合はvue add unit-jest
で後から追加ができます。
次にjest.config.js
への設定値記述についてです。
公式のガイドの方にjestの設定の必要項目について書かれていますが、
@vue/cli-plugin-unit-jest
導入済みなら、こちらはpresetにtypescript-and-babel
を指定してあげれば良いことがわかりました。
既に書いたUnit Testingオプション付きで vue create project
、
またはvue add unit-jest
した場合こちらは初期で指定されており、同時にテストのサンプルファイルも用意してくれていますね。
このpresetですが、実際に中身を見ると基本的な設定をだいたい網羅してくれていることがわかります。
まるっとテストに必要な機構を用意してくれていて、いたれりつくせりといった感じですね。
自分の場合の設定ファイルの中身は必要最低限のところで以下で落ち着きました。
module.exports = {
preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel',
testMatch: ['<rootDir>/src/**/*.spec.ts'],
snapshotSerializers: ['jest-serializer-vue']
};
テストを書く
実際テストを書いてみるに当たり、どんなプラクティスがあるのかと関連記事を閲覧して周ったりVuetifyのテストファイルを参考にしてみたりしました。
書いてみた結果の簡単な例ですが、
まずは必要なモジュールのインポート。
import { createLocalVue, mount, Wrapper, MountOptions } from '@vue/test-utils';
import SampleComponent from '@/components/SampleComponent.vue';
公式にあるようにfactoryメソッドを定義し、 このmountFunction
をbeforeEach内で作成しておきます。
const localVue = createLocalVue();
let mountFunction: (
options?: MountOptions<SampleComponent>
) => Wrapper<SampleComponent>;
beforeEach(() => {
mountFunction = (options = {}) => {
return mount(SampleComponent, {
localVue,
...options
});
};
});
vuexを注入した場合
import Vuex from 'vuex';
import { SampleModule } from '@/store/SampleModule';
const localVue = createLocalVue();
localVue.use(Vuex);
const createStore = () => {
return new Vuex.Store({
modules: {
SampleModule
}
});
};
let mountFunction: (
options?: MountOptions<SampleComponent>
) => Wrapper<SampleComponent>;
beforeEach(async () => {
mountFunction = (options = {}) => {
const store = createStore();
return mount(SampleComponent, {
store,
localVue,
...options
});
};
});
vuexの注入に関しては悩みました。
実際書き方のパターンを模索してみる中で、以下のような原則で実装しておくとテストが書きやすそうだと感じました。
- コンポーネントのvuexへの依存を避けて、可能な限りpropsで値を取るべき。
- storeはmodule化しておく。
propsに関してなのですが、値固定ならbeforeEach内でセットしてしまうのもありかと思います。
または初期値を事前に定義しておいてテスト毎にセットするとテストパターンに応じて柔軟性が持てそうです。
const initialProps = {
propsData: {
id: '12345',
name: 'namae'
}
};
使えるAPIの種類や利用例については公式が充実しているので、ここでは簡単な例のテストを2つだけ示したいと思います。
画面上部にバーガーメニューがあり、クリックしたら開くかどうか、また開いた状態のsnapShotを取得するテスト例です。
isOpenMenuと
いうdataを定義し、メニューの開閉状態の制御に使用していることとします。
test('スナップショット(メニューを開いた状態)', () => {
const wrapper = mountFunction({
...initialProps,
data: () => ({
isOpenMenu: true
})
});
expect(wrapper.element).toMatchSnapshot();
});
test('バーガーボタンをクリックするとメニューが開くこと', async () => {
const wrapper = mountFunction(initialProps);
wrapper.find('.burger_btn').trigger('click');
await flushPrimises();
expect(wrapper.find('.menu > ul').isVisible()).toBe(true);
});
flushPromise
でクリック後のdomの更新を待っています。これについては別途パッケージのインストールが必要で、詳しくは公式をご覧ください。
.find().isVisible()
で開いたメニュー内の要素が描画されているかを見ています。
以上、簡単ではありますがvueの単一ファイルコンポーネントのテストを書いてみたメモでした。