RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

Vue/Jestテストのハマりどころ3選!!

はじめに

こんにちは。choreiiです。最近自チームで扱っている商材のフロントエンドのテストコードを大量に書く機会がありました。その中で大きくハマった3点について紹介します。

環境

  • Vue:2.6.11
  • vue-test-utils:1.0.0-beta.29
  • Jest:23.6.0

1. ライフサイクルフックをmock化(上書き)したい

以下のようにbeforeMountで初期化処理を書いている場合、beforeMountをまるごとmock化(上書き)したくなる時があります。

<script>
export default {
  beforeMount() {
    // コンポーネントで必要なデータの取得や初期化の処理
  },
  methods: {
    sampleMethods() {
      // 何らかの処理
    }
  }
};
</script>

methodsの内容を上書きする時は以下のようにすれば差し替えることができるので、ライフサイクルフックの場合も上書きできると考えていたのですができませんでした。

test("test", () => {
  const wrapper = shallowMount(Sample, {
    beforeMount() {
        // これで空の内容で上書きできるはず -> できない。。。
    }
    methods: {
      sampleMethods() {} // 空の内容で上書きできる
    }
  });
});

vue-test-utilsのガイドには特に記載がなかったので手元で色々頑張ってみたのですが、下記のissueを発見。

github.com

ライフサイクルフックは上書きできないので、処理を上書きしたい場合はbeforeMountの処理をmethodsに切り出して、その切り出した内容を上書きするのが良さそうです。

<script>
export default {
  beforeMount() {
    this.initData(); // methodsに切り出し
  },
  methods: {
    initData() {
        // コンポーネントで必要なデータの取得や初期化の処理
    },
    sampleMethods() {
      // 何らかの処理
    }
  }
};
</script>
test("test", () => {
  const wrapper = shallowMount(Sample, {
    methods: {
      initData() {}, // methodsの方で上書き
      sampleMethods() {}
    }
  });
});

ちなみに、shallowMount(mount)した後から、setMethodsを使っても上書きできます。

test("test", () => {
  const wrapper = shallowMount(Sample);
  wrapper.setMethods({
      initData(): {}  // 後から上書き
  })
});

2. テストによってcomputedを差し替えたい

条件が少しだけ異なるテストを書く際に、毎回shallowMount(mount)を行うのはコード記述が増えるのであまりやりたくないです。methodsやcomputedだけを後から差し替える方法があれば完璧です。上記で述べたように、methodsは後から上書きすることができます。

describe("test", () => {
  const wrapper = shallowMount(Sample);

  test("test1", () => {
      wrapper.setMethods({
          sampleMethods(): {}  // test1用の処理
      })
  });

  test("test2", () => {
      wrapper.setMethods({
          sampleMethods(): {}  // test2用の処理
      })
  });
});

setMethodsがあるならsetComputedもあるだろうと考えていましたが、これまたvue-test-utilsのガイドには記載がありません。

f:id:choreii:20200204103153p:plain
ガイド記載のwrapperのメソッド抜粋

他にもset系のメソッドが用意されていながら、computedだけがありません。。。嫌な予感がしながら情報を探してみると下記のissueを発見(デジャヴ)。

github.com

昔は用意されていたものの、バグがテストをすり抜ける可能性があるので廃止されたようです。バグをなくすためと言われては仕方がないのでテストごとにshallowMount(mount)をするようにしています。

describe("test", () => {
  const render = ((sampleComputedStub), {
      const wrapper = shallowMount(Sample, {
        computed: {
          sampleComputed: sampleComputedStub
        }
      });
  });

  test("test1", () => {
      const wrapper = render({
          // sampleComputedを上書きしたい処理
      }):
  });

  test("test2", () => {
      const wrapper = render({
          // sampleComputedを上書きしたい処理
      }):
  });
});

3. localStorageをmock化したい

テストでlocalStorageをそのまま使うわけにはいかないので、mockに差し替える必要があります。

localStorageについては以下を参照

developer.mozilla.org

jest.spyOn(localStorage, 'setItem');

spyOnの使い方は間違えていないのに、以下のようなエラーがでてしまいました。

TypeError: object[methodName].mockImplementation is not a function

localStorageの中で、setItemが見つからないみたいです。またまた嫌な予感がしましたが今回は解決方法がありました。

stackoverflow.com

以下のようにprotoを使用するとアクセスできるようです。ただリンク先にも記載がある通り、protoは非推奨とのことなのであまり多用するのはよろしくなさそう。今回はどうしてもjest.spyOnを使って呼び出し回数や呼び出し引数のテストを書きたかったので使用しています。

jest.spyOn(localStorage.__proto__, 'setItem');

まとめ

Vue/Jestの書き方は公式のガイドが充実しているので、ガイド通りに進めている間はスムーズに進みます。その反面、ガイドに載っていないことをやろうとすると情報がなかなか見つからず痛い目をみることが多かったです。

テストの主目的はプロダクトコードの品質担保なので、あまりテストの書き方を調べるのに時間をかけたくありません。今後は多少記述が増えたり冗長になったとしても、ガイドに従いながらテストを書き、どうしても実現できないことだけ別途調査することにします。

参考

Copyright © RAKUS Co., Ltd. All rights reserved.