Vue.js 単一ファイルコンポーネントのテストを書いてみたメモ

Vue Test Utilsを使ったvue.jsの単一ファイルコンポーネントのテストに興味があったのですが、あまり書く機会がなかったので今回個人的に書いてみた感じのメモです。

## はじめに
vueは2系、使用したテストランナーはjestです。
[Vue Test Utils : テストランナを選ぶ](https://vue-test-utils.vuejs.org/ja/installation/choosing-a-test-runner.html)

他、typeScriptを使用、またVuexの注入について軽く触れます。

また、セットアップの流れは[公式](https://vue-test-utils.vuejs.org/ja/installation/testing-single-file-components-with-jest.html)に詳しいのですが、個人的にパッケージ周りとjestの設定について調べてみてわかったことを書きます。

## セットアップ
パッケージに関しては、`@vue/test-utils`をはじめ、jestに.vueファイルの処理方法を教えるために必要な`vue.jest`や他`babel-jest` `ts-jest` `jest-serializer-vue`などは、
既に[@vue/cli-plugin-unit-jest](https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/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`への設定値記述についてです。
[公式のガイド](https://vue-test-utils.vuejs.org/ja/guides/#typescript-%E3%81%A8%E4%B8%80%E7%B7%92%E3%81%AB%E4%BD%BF%E3%81%86)の方にjestの設定の必要項目について書かれていますが、
`@vue/cli-plugin-unit-jest`導入済みなら、こちらはpresetに`typescript-and-babel` を指定してあげれば良いことがわかりました。
既に書いた`Unit Testingオプション付きで vue create project`、
または`vue add unit-jest`した場合こちらは初期で指定されており、同時にテストのサンプルファイルも用意してくれていますね。
このpresetですが、[実際に中身を見ると](https://github.com/vuejs/vue-cli/blob/dev/packages/%40vue/cli-plugin-unit-jest/presets/typescript-and-babel/jest-preset.js)基本的な設定をだいたい網羅してくれていることがわかります。


まるっとテストに必要な機構を用意してくれていて、いたれりつくせりといった感じですね。
自分の場合の設定ファイルの中身は必要最低限のところで以下で落ち着きました。

```javascript:jest.config.js
module.exports = {
  preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel',
  testMatch: ['<rootDir>/src/**/*.spec.ts'],
  snapshotSerializers: ['jest-serializer-vue']
};
```

## テストを書く
実際テストを書いてみるに当たり、どんなプラクティスがあるのかと関連記事を閲覧して周ったりVuetifyのテストファイルを参考にしてみたりしました。

書いてみた結果の簡単な例ですが、
まずは必要なモジュールのインポート。

```typescript:SampleComponent.spec.ts
import { createLocalVue, mount, Wrapper, MountOptions } from '@vue/test-utils';
import SampleComponent from '@/components/SampleComponent.vue';
```

公式にあるようにfactoryメソッドを定義し、 この`mountFunction` をbeforeEach内で作成しておきます。

```typescript:SampleComponent.spec.ts
const localVue = createLocalVue();
let mountFunction: (
  options?: MountOptions<SampleComponent>
) => Wrapper<SampleComponent>;

beforeEach(() => {
  mountFunction = (options = {}) => {
    return mount(SampleComponent, {
      localVue,
      ...options
    });
  };
});
```

vuexを注入した場合

```typescript:SampleComponent.spec.ts
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内でセットしてしまうのもありかと思います。
または初期値を事前に定義しておいてテスト毎にセットするとテストパターンに応じて柔軟性が持てそうです。

```typescript:SampleComponent.spec.ts
const initialProps = {
  propsData: {
    id: '12345',
    name: 'namae'
  }
};
```

使えるAPIの種類や利用例については公式が充実しているので、ここでは簡単な例のテストを2つだけ示したいと思います。
画面上部にバーガーメニューがあり、クリックしたら開くかどうか、また開いた状態のsnapShotを取得するテスト例です。
`isOpenMenuと`いうdataを定義し、メニューの開閉状態の制御に使用していることとします。

```typescript:SampleComponent.spec.ts
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の更新を待っています。これについては別途パッケージのインストールが必要で、[詳しくは公式](https://vue-test-utils.vuejs.org/ja/guides/testing-async-components.html#:~:text=flush-promises)をご覧ください。

`.find().isVisible()`で開いたメニュー内の要素が描画されているかを見ています。

以上、簡単ではありますがvueの単一ファイルコンポーネントのテストを書いてみたメモでした。