25
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

vue-test-utilsとjestでLifecycle Hookのメソッドをモックする

Last updated at Posted at 2019-03-31

本記事ではvue-test-utilsとjestでvue.jsのテストを書くときにLifecycle Hook(mountedなど)のメソッドの処理をモックする方法を紹介します。
結論から言うと、mountedフック自体をモックするのではなく、mountedフック内の処理をメソッド化してそのメソッドをモックすることで対応します。

動作環境

主なフレームワーク/ライブラリのバージョンです。

フレームワーク/ライブラリ バージョン
vue 2.6.10
vue-test-utils 1.0.0-beta.29
jest 24.5.0

やりたいこと

componentの単体テストを行う場合にmountedの処理をモックしたい。
以下のような場合を想定。

  • mountedの中で複雑な処理やapiを何度も呼ぶような準備コストが大きい
  • vueのコンポーネントの外にあるhtml内のjavascriptの変数を読み込んで初期化しているような場合

サンプル

コンポーネント

例として以下のCounter.vueのmountedの処理をmockすることを考えます。

Counter.vue
<template>
  <div>
    <h1>Click and Count</h1>
    <button v-on:click="increment">+1</button>
  </div>
</template>

<script>
export default {
  name: 'Counter',
  data() {
    return {
      count: 0,
    };
  },
  mounted() {
    // 例) このコンポーネントの外にあるhtmlに直接埋め込まれた
    //    $initialCountという変数の値をcountに設定して初期化する
    this.count = $initialCount;
  },
  methods: {
    increment() {
      this.count += 1;
    }
  },
};
</script>

コンポーネントのテスト

以下のようにbuttonをclickするとカウントアップされることをテスト。

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

describe('Counter.vue', () => {
  it('click button and countup', () => {
    const wrapper = shallowMount(Counter);
    const beforeCount = wrapper.vm.count;
    wrapper.find('button').trigger('click');

    expect(wrapper.vm.count).toBe(beforeCount + 1);
  });
});

今のままだと$initialCountの変数が定義されていないのでテストしたい処理にたどり着く前に失敗してしまう。
スクリーンショット 2019-03-30 20.19.29.png

初期化処理をメソッド化してモックする

外部から取得する変数自体をメソッド化してしまえばモックできるので、メッソッド化します。

修正後のコンポーネント

Counter.vue
<template>
  <div>
    <h1>Click and Count</h1>
    <button v-on:click="increment">+1</button>
  </div>
</template>

<script>
export default {
  name: 'Counter',
  data() {
    return {
      count: 0,
    };
  },
  mounted() {
    // 例) このコンポーネントの外にあるhtmlに直接埋め込まれた
    //    $initialCountという変数の値をcountに設定して初期化する
-   // this.msg = $initialCount;
+    this.count = this.initialCount();
  },
  methods: {
+   initialCount() {
+     return $initialCount;
+   },
    increment() {
      this.count += 1;
    }
  },
};
</script>

修正後のテスト

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

describe('Counter.vue', () => {
  it('click button and countup', () => {
    const count = 10;
    // initialCount()をmock
    const initialCount = jest.fn();
    initialCount.mockReturnValue(count);
    // methodsとして渡すことでinitialCountが呼ばれるとmockに設定した戻り値が返る
    const wrapper = shallowMount(Counter, { methods: { initialCount } });
    // クリックしてカウントされることを確認
    wrapper.find('button').trigger('click');
    expect(wrapper.vm.count).toBe(count + 1);
  });
});

結果

無事にテストが通りました。

スクリーンショット 2019-03-30 20.49.59.png

まとめ

mountedやcreatedなどのライフサイクルフック内の処理が準備できないような場合(そもそものつくりが悪いというのもあるかもしれませんが)は、処理をメソッド化することで回避する方法を紹介しました。
もっとスマートなやり方があるよなどあればばコメントいただけると幸いです。

余談

最初はmounted自体を上書けると思い、以下のようなコードで試しましてみましたが元のmountedの処理が動作してしまいテストに失敗しました。

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

describe('Counter.vue', () => {
  it('click button and countup', () => {
    const count = 0;
    const mounted = jest.fn();
    // mountedにjestのmock関数を置く
    const wrapper = shallowMount(Counter, { mounted });
    wrapper.find('button').trigger('click');
    expect(wrapper.vm.count).toBe(count + 1);
  });
});

少しソースを修正してもう少し中の動きを身を見てみると

Counter.vue
<template>
// ・・・
</template>

<script>
export default {
  name: 'Counter',
  data() {
    // ・・・
  },
  mounted() {
    // this.count = $initialCount;
    // 動作を確認するためのログ
    console.log('original mounted');
  },
  methods: {
    // ・・・
  },
};
</script>

もともとのmountedとmountedオプションを設定した二つのwrapperでログを確認してみる

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

describe('Counter.vue', () => {
  it('click button and countup', () => {
    const mounted = () => {
      // 動作を確認するためのログ
      console.log('mock mounted');
    };

    // mountedオプションを設定しない
    const wrapper1 = shallowMount(Counter);
    console.log(wrapper1.vm.$options.mounted);

    // mountedオプションを設定する
    const wrapper2 = shallowMount(Counter, { mounted });
    console.log(wrapper2.vm.$options.mounted);
  });
});

mountedを追加したwrapper2 のほうは、wrapper2.vm.$options.mountedで二つの関数が返ってきています。shallowMount時に追加した関数で上書くのではなく追加され、mouted()内のログからoriginalmock両方が動いているようでした。

スクリーンショット 2019-03-31 11.52.01.png

少し古いやりとりですが、
https://github.com/vuejs/vue-test-utils/issues/148
でライフサイクルフックのモックについてのやりとりがされていて、vue.jsコアチームのEdd Yerburgh
いわく

I think we should add support for mocking all lifecycle events, or none.

とあるので追加されてこの辺りも改善されることを期待です。

25
16
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?