Help us understand the problem. What is going on with this article?

Vueでよく書くユニットテストのパターン

Vueのコンポーネントのユニットテストでよく書くパターンを紹介します。
環境はvue-cliで生成したものをそのまま使っています。

APIの詳細な説明はVue Test Utilsを参照してください。

スナップショットテスト

出力されるHTMLが予期せず変更されないようにする場合に使うテストです。

GachaMv.spec.ts
it("itemsの値がhtmlに出力されているか?", () => {
  const items = [
    new Item("アイテム", 3, "アイテム説明")
  ];
  const wrapper = shallowMount(GachaMv, {
    propsData: { items }
  });
  expect(wrapper.html()).toMatchSnapshot();
});

上記のテストを追加しnpm run test:unit or yarn test:unitを実行するとテストファイルと同階層に__snapshots__/GachaMv.spec.ts.snapが出力されます。

GachaMv.spec.ts.snap
exports[`GachaMv.vue itemsの値がhtmlに出力されているか? 1`] = `
<div class="gacha-mv">
  <slick-stub options="[object Object]">
    <div class="gacha-mv-list">
      <p class="gacha-mv-list-name">アイテム</p>
      <p class="gacha-mv-list-rare">3</p>
      <p class="gacha-mv-list-description">アイテム説明</p>
    </div>
  </slick-stub>
</div>
`;

shallowMountではなくmountを使用した場合は、以下のように子コンポーネントも展開した状態で出力されます。

GachaMv.spec.ts.snap
exports[`GachaMv.vue itemsの値がhtmlに出力されているか? 1`] = `
<div class="gacha-mv">
  <div class="slick-initialized slick-slider">
    <div class="slick-list draggable">
      <div class="slick-track" style="opacity: 1; width: 0px; transform: translate(0px, 0px);">
        <div class="slick-slide slick-current slick-active" data-slick-index="0" aria-hidden="false" style="margin-left: 0px; width: 0px;">
          <div>
            <div class="gacha-mv-list" style="width: 100%; display: inline-block;">
              <p class="gacha-mv-list-name">アイテム名</p>
              <p class="gacha-mv-list-rare">3</p>
              <p class="gacha-mv-list-description">アイテム説明</p>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
`;

以下のようにshallowMountとstubsを組み合わせることで、一部のコンポーネントのみスタブすることも可能です。

GachaMv.spec.ts.snap
const wrapper = shallowMount(GachaMv, {
  stubs: {
    'slick': Slick,
  },
}

computedが期待した値を返しているか?

computedが正しい値を確認しているかのテストです。

GachaMv.spec.ts
it("pickupItemが期待通りか?", async() => {
  const items = [
    new Item("アイテム名3", 3, "アイテム説明3"),
    new Item("アイテム名4", 4, "アイテム説明4"),
    new Item("アイテム名5", 5, "アイテム説明5"),
  ];
  const wrapper = shallowMount(GachaMv, {
    propsData: { items }
  });
  expect((wrapper.vm as any).pickupItems).toEqual([new Item("アイテム名5", 5, "アイテム説明5")]);
});

クリックイベントが動作しているか?

要素がイベントを発火した時に、定義した関数が実行されているかのテストです。
スタブにsinonを使ってます。

GachaPlay.spec.ts
it(".gacha-playクリック時にonPlayGachaが実行されるか?", () => {
  const onPlayGachaStub = sinon.stub();
  const wrapper = shallowMount(GachaPlay, {
    propsData: { 
      number: 1,
      onPlayGacha: onPlayGachaStub
    }
  });
  wrapper.find(".gacha-play").trigger('click');
  expect(onPlayGachaStub.called).toBe(true);
  onPlayGachaStub.restore();
});

sinon or jest.fn

先ほどのクリックイベントのテストでsinonを使いましたが、jest.fnでも同じことができます。
個人的は使い慣れているsinonを使用しています。

unit testing - stubbing a function using jest - Stack Overflow

非同期のテスト

非同期テストでは、flush-promisesやVue.nextTickを使わないと期待した結果が得られません。

flush-promisesについて

flush-promises

保留中の解決済みの約束のハンドラをすべてフラッシュします。

やっていることはmicrotasks または macrotasksのプロミスを返しているだけです。(setImmediate関数がある場合はmicrotasksが選択されるので、これがDOMの更新を待たなかった原因かもしれません。)

microtasks、macrotasksについは以下の記事が分かりやすかったです。

Tasks, microtasks, queues and schedules - JakeArchibald.com

Vue.nextTickについて

Vue.nextTick

callbackを延期し、DOMの更新サイクル後に実行します。DOM更新を待ち受けるために、いくつかのデータを変更した直後に使用してください。

テスト記述例

VueのDOMの更新後の値をテストする場合はnextTick、非同期処理(Promiseをfulfilled)する場合はflushPromisesを使用します。

it('test', async() => {
  const wrapper = shallowMount(Foo);
  await flushPromises(); // マウント時などに非同期処理がある場合
  expect(wrapper.vm.text).toBe('マウント時のテキスト');
  // expect(wrapper.find('text').text()).toBe('マウント時のテキスト'); // DOMが更新される保証はないので、Errorになるかも

  wrapper.find('button').trigger('click'); // クリックイベントで非同期処理を実行

  await wrapper.vm.$nextTick(); // DOMの更新を保証
  expect(wrapper.find('text').text()).toBe('クリック後のテキスト');
})

さいごに

簡単なコンポーネントのテストであれば、これらの組み合わせで十分かと思います。
今回のユニットテストではUIの崩れなど検知できないので、以下のリンク先で紹介されているようなstorybookやvisual regressionテストなどの導入を検討してみてください。

Storybookとvue-i18nで多言語確認を容易にしよう - スタディスト開発ブログ - Medium

Storybookとreg-suitで気軽にはじめるVisual Regression Testing - wadackel.me

kitanote
編集したコードが即時画面に反映されるFWが好きです。 趣味で株やってましたがコロナショックで瀕死。
weblio
「Weblio辞書」「Weblio英会話」の運営企業。人々の選択肢を広げる新たなサービスに挑戦中!
https://www.weblio-inc.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした