【Vue】Jestを使った Vue / TypeScript の単体試験 ~ 入門編 ~

■ はじめに

https://dk521123.hatenablog.com/entry/2020/12/23/103209
https://dk521123.hatenablog.com/entry/2020/12/22/192553

の続き。

Vueでの単体試験を行う必要があるので、メモ。

目次

【1】環境設定
【2】(個人的に)はまったこと
 その1:@Component について
 その2:wrapper.vm について
【3】Tips
 1)単体試験ファイルを単体で実行したい場合
 2)カバレッジを計測するためには
 3)mount / shallowMount
【4】サンプル

【1】環境設定

https://dk521123.hatenablog.com/entry/2020/12/22/192553

の「例:vue create コマンド」で
 (*) TypeScript
 (*) Unit Testing
などを設定したらできた。

実行方法

コンソール画面で、以下をコマンドして実行する。
~~~~~~~~~~~~
npm run test:unit
~~~~~~~~~~~~

【2】(個人的に)はまったこと

その1:@Component について

単体試験でうまくいかなくて、はまった。
原因は、Classの定義に「@Component」をつけてなかったせい。

該当部分・抜粋

@Component // <= ここをつけなくてずっとはまってた
export default class HelloWorld extends Vue {

その2:wrapper.vm について

methodsなどのテストで色々なサイト見たが
うまく単体試験から対象メソッドが呼び出せなかった。

該当部分・抜粋

// const result = wrapper.vm.getHello(inputName);
// 多分、もっといい方法があるだろーがとりあえず以下で対応
const result = (wrapper.vm as any).getHello(inputName);

【3】Tips

* jest のコマンドのオプションは以下を参照。

https://jestjs.io/ja/docs/cli

1)単体試験ファイルを単体で実行したい場合

# -u : --updateSnapshot
#  => テスト実行中に失敗した全てのスナップショットを再取得する
npm run  test:unit -- -u -t="Home"

2)カバレッジを計測するためには

npm run  test:unit -- --coverage --verbose --silent

参考文献
https://qiita.com/qoAop/items/469a6cf05a7c4c04f020

3)mount / shallowMount

# Method Names Explanations
1 mount コンポーネントを描画する(本番に近いテストができる分、複雑化する)
2 shallowMount コンポーネントをスタブ化する(純粋な単体試験だとこっち)

【4】サンプル

ファイル構成

my-app << 対象プロジェクト
 + src
  |  + components
  |      + HelloWorld.vue << テスト対象Vueファイル
 + tests/unit
     + components
         + HelloWorld.spec.ts << 単体テストファイル

HelloWorld.vue

<template>
  <div class="hello">
    <div v-if="isShown">
      <h1>Hello world</h1>
    </div>
    <p>{{text}}</p>
  </div>
</template>
 
<script lang="ts">
import { Component, Prop, Emit, Watch, Vue } from "vue-property-decorator";
import { component } from "vue/types/umd";

@Component
export default class HelloWorld extends Vue {
  // props
  @Prop() private isShown:boolean;

  // data
  @Prop() text!: string;
  value: string = this.text;

  // watch
  @Watch('value')
  onChangeValue(newValue: string, oldValue: string): void {
    console.info(`watch: ${newValue}, ${oldValue}`);
  }

  // computed
  public get isNullOrEmptyText(): boolean {
    return this.text === undefined ||
      this.text === null ||
      this.text.length === 0;
  }
  // methods
  getHello(name: string): string {
    return `Hello, ${name}!!`;
  }
}
</script>

HelloWorld.spec.ts

import { shallowMount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'

describe('HelloWorld.vue', () => {
  // pros
  describe('pros', () => {
    // 表示・非表示のテスト(v-if)
    it('isShown - true', () => {
      const isShown = true;
      const wrapper = shallowMount(HelloWorld, {
        propsData: { isShown }
      });
      expect(wrapper.html()).toContain("Hello world");
    });
    it('isShown - false', () => {
      const isShown = false;
      const wrapper = shallowMount(HelloWorld, {
        propsData: { isShown }
      });
      expect(wrapper.html()).not.toContain("Hello world");
    });
  });
  // computed
  describe('computed', () => {
    it('isNullOrEmptyText - return true', () => {
      const text = null;
      const wrapper = shallowMount(HelloWorld, {
        propsData: { text }
      });
      const vm = wrapper.vm as any
      const result = vm.isNullOrEmptyText;
      expect(result).toBeTruthy();
    });
  });
  // methods
  describe('methods', () => {
    it('getHello', () => {
      const wrapper = shallowMount(HelloWorld);
      const inputName = 'Mike';
      const expected = 'Hello, Mike!!';
      const vm = wrapper.vm as any
      const result = vm.getHello(inputName);
      expect(result).toMatch(expected);
    });
  });
});

実行例

実行コマンド例

# 対象プロジェクトに入る
cd my-app

# 実行
npm run test:unit

実行結果例

> my-app@0.1.0 test:unit
> vue-cli-service test:unit

(node:16204) [DEP0148] DeprecationWarning: Use of deprecated folder mapping "./" in the "exports" field module resolution of the package 
at C:\xxxx\my-app\node_modules\vuex\package.json.
Update this package.json to use a subpath pattern like "./*".
ing was created)                                          ing was created)
 PASS  tests/unit/components/HelloWorld.spec.ts
  HelloWorld.vue
    pros
      √ props (14ms)
    computed
      √ isNullOrEmptyText - return true (2ms)
    methods
      √ getHello (2ms)

Test Suites: 1 passed, 1 total
Tests:       3 passed, 3 total
Snapshots:   0 total
Time:        4.315s
Ran all test suites.

参考文献

https://mae.chab.in/archives/60167

は、めちゃくちゃ参考になった。

シンプルなサンプル(Github
https://github.com/vuejs/vue-test-utils-typescript-example

関連記事

Jestを使った Vue / TypeScript の単体試験 ~ 基本編 ~
https://dk521123.hatenablog.com/entry/2021/01/27/225148
Jestを使った Vue / TypeScript の単体試験 ~ 非同期編 ~
https://dk521123.hatenablog.com/entry/2021/03/13/000000
Jestを使った Vue / TypeScript の単体試験 ~ axios 編 ~
https://dk521123.hatenablog.com/entry/2021/01/16/202822
Jestを使った Vue / TypeScript の単体試験 ~ axios / あれこれ 編 ~
https://dk521123.hatenablog.com/entry/2021/03/26/225814
単体試験ツール Jest ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2020/12/23/103209
JS単体試験ツール Jest ~ Mock編 ~
https://dk521123.hatenablog.com/entry/2021/01/26/215558
JS単体試験ツール Jest ~ キャッシュ関連 ~
https://dk521123.hatenablog.com/entry/2021/03/12/164932
Vue CLI ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2020/12/22/192553
Vue + NightWatch で E2Eテスト をする
https://dk521123.hatenablog.com/entry/2021/02/06/220603