Vue.js で関数テストとパラメータライズドテストした雑記（Vue Test Utils + Jest）

フロントエンドのテストにも力を入れていきたいと思っていた矢先、
Vue Test Utils なる物が、2020年5月に正式リリースしていたという事実を知ったので触ってみた雑記。
_（ほんとうに雑なので注意）_

おまけで最後に、環境構築時にハマったポイント集も付けている。

# 知りたかった/やりたかったこと

- どうやってテストするのだ
- 関数をテストしたいのだ
- パラメータライズドテストをしたいのだ


Vue Test Utils 公式ドキュメントの[日本語訳版](https://vue-test-utils.vuejs.org/ja/)を参考に、これらの事を試していった。
_※後述するが、2020/08/30 時点では英語ドキュメントのほうを見て入門するのがオススメ。_

なお、書いたコードは GitHub にて公開している。
https://github.com/okajax/vue-test-utils-playground

## どうやってテストするのだ

Vue Test Utils 自体は、Vue.js コンポーネントのテストがしやすくなるAPIを提供するライブラリの模様。
テスト自体は、お好みのテストランナーを使用できる。今回はJestを使用。

```Counter.test.js
import { mount } from '@vue/test-utils';
import Counter from './Counter';

describe('Counter', () => {
  const wrapper = mount(Counter);

  it('counter exists', () => {
    const el = wrapper.find('span.count');
    expect(el.exists()).toBe(true);
  });

  it('button exists', () => {
    const el = wrapper.find('button');
    expect(el.exists()).toBe(true);
  });

  it('click increment button', () => {
    const button = wrapper.find('button');
    const init = wrapper.vm.$data.count;
    button.trigger('click');
    const incremented = wrapper.vm.$data.count;

    expect(incremented).toBeGreaterThan(init);
  });

});

```

以下、解説。
(そもそもJest自体を初めて触る。。ので、そのあたりも解説)

### Wrapperオブジェクト

こちらが Vue Test Utils によって提供されているもの。
`mount(component)` によって、 [Wrapperオブジェクト](https://vue-test-utils.vuejs.org/ja/api/wrapper/)が返ってくる。
これを用いて、props/data へのアクセス、要素の有無調査、イベント発火といった操作が可能になる。

### describe

`describe(name, fn)` は必須ではなく、テストをグループ化したいときに使う。
setup/teardown を describe 単位で行う事も可能。
https://jestjs.io/docs/ja/api#describename-fn

### it

`it(name, fn)` が1つのテスト。
※ `test(name, fn)` でも同じ。

### expect

`expect(actual).toBe(expected)` がいわゆるアサーション。

　
以上が、基本の形のようだ。


## 関数をテストしたいのだ

やはりテストしたいのは methods なのである。
先述の例のように、要素の click イベントを経由して呼ぶ事も可能だが、 methods を直接呼び出す事も可能。

下記は、コンポーネント内の data を操作するような method のテスト。

```List.test.js
import { mount } from '@vue/test-utils';
import List from './List';

describe('List', () => {
  const wrapper = mount(List);

  it('sort by asc', () => {
    const expected = [
      'apple',
      'banana',
      'cola'
    ];
    wrapper.vm.sort();
    expect(wrapper.vm.$data.items).toStrictEqual(expected);
  });

  it('sort by desc', () => {
    const expected = [
      'cola',
      'banana',
      'apple'
    ];
    wrapper.vm.sort(true); // ture を引数にとると降順ソートする
    expect(wrapper.vm.$data.items).toStrictEqual(expected);
  });

});
```


## パラメータライズドテストをしたいのだ

しきい値テストのように、実行コードは変わらないが、複数の引数でテストしたいときがある。
そうしたテストをパラメータライズドテストと呼ぶ。
_（Go ではテーブルドリブンテストとも）_

これを

- Jest の `it.each(table)(name, fn, timeout)`
- Wrapper (Vue Test Utils) の `setProps(object)`

を組み合わせて行った。テスト対象は下記。

- スコアの値によって、ランクを表示するコンポーネント
  - score プロパティーを受け取る
  - rank という computed property を持っている
     - デフォルトでは 1 を、score が 50 以上なら 2 を、score が 100 以上なら 3 を表示する

```Rank.test.js
import { mount } from '@vue/test-utils';
import Rank from './Rank';

describe('Rank', () => {
  const wrapper = mount(Rank);

  it.each([
    [1, 1],
    [49, 1],
    [50, 2],
    [51, 2],
    [99, 2],
    [100, 3],
    [101, 3],
  ])('rank when score is %d', async (score, expected) => {
    await wrapper.setProps({ score });
    expect(wrapper.vm.rank).toBe(expected);
  });

});

```

ちなみに `setData(object)` もあるので、data も操作できる。

# 3行まとめ

- フロントエンドのテストに疎く、E2Eテストぐらいしか無いのだろうかと思っていた。
- Vue Test Utils によって、コンポーネント単位のテストが実現できると知れた。
- 関数のテスト、パラメータライズドテストも問題なくできるので、嬉しい。

以上、おしまい。テストばんばん書くぞー。


# おまけ：ハマったポイント集

テストが実行できるようになるまで、色々コケたので忘備録。

## import が使えないエラー

```
import { mount } from '@vue/test-utils';
^^^^^^
SyntaxError: Cannot use import statement outside a module
```

babeれていない。
_（単純にドキュメントの見逃し）_

`npm i -D babel-preset-env` し、 `.babelrc` （[※参考](https://vue-test-utils.vuejs.org/ja/installation/#jest-%E3%82%92%E4%BD%BF%E7%94%A8%E3%81%97%E3%81%9F%E5%8D%98%E4%B8%80%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%81%AE%E3%83%86%E3%82%B9%E3%83%88)） を入れて解決。

### そもそもbabel-preset-envとはなんぞや

- https://github.com/babel/babel-preset-env
- https://qiita.com/ryuone/items/13f5d450c3865709ba10

雑理解：Babel設定をぼくの代わりに用意してくれる君


## Cannot find module 'babel-core' というエラー

`@babel/core` をインストールしているのに、vue-jest が一向に見つけてくれない。
https://qiita.com/kawadumax/items/48568f8bdff7a65e3f46 を参考に解決。

そもそも日本語ドキュメントが古かった。英語のドキュメントは、この問題について言及している。


## deprecate warning がやたら出てくる

```
[vue-test-utils]: contains is deprecated and will be removed in the next major version. Use `wrapper.find`, `wrapper.findComponen
t` or `wrapper.get` instead.
```

こちらも、日本語ドキュメントが古いのが要因。
廃止予定の関数が、チュートリアルに使われている。

---
スタートを切るまでに、そこそこハマった。
2020/8/30 時点では、英語ドキュメントのほうを見て入門するほうが良い。

下記に package.json も置いているので、これから試そうという方は参考にしていただけたら嬉しい。
https://github.com/okajax/vue-test-utils-playground
　
# FYI

- [Vue Testing Handbook](https://lmiller1990.github.io/vue-testing-handbook/)
  - Vue Test Utils のメンテナーである lmiller1990 氏によるガイド。Vuex や Vue Router のテストについても書いている。サンプルコードも多い。「こういうとき、どうやるんだろう」と迷ったら見たい。ていうか見る。