【Vue3 + Jest】でユニットテストを作成する – ②Mock を使った単体テスト –

前回は下記の記事で Vue 3 + Jest でのテストを実行できる状態にまでしました。

今回は compositionAPI で実装して Store や Router を使っているページのテストを書いていきます。

テスト対象のページ

Target.vue というファイルが今回のテスト対象だとします。

下記のように Store や Router を使っているコンポーネントになります。

import TargetClient from '@/client/targetClient'
import { useStore } from 'vuex'
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'

//略

setup() {
    const router = useRouter()
    const store = useStore()
    const targetClient = TargetClient(store)

    const target = ref(null)

    onMounted(async () => {
      const response = await targetClient.getTarget()
      target.value = response.data.target
    })


    const edit = () => {
       router.push({ name: 'Edit'})
    }

//以下省略

TargetClient は バックエンドの API を叩く用のモジュールになります。テストを書く際にはこのモジュールもモックします。

onMounted のタイミングでデータを取得してきてその値に応じて画面にレンダリングしていくような形です。

テストコード

以下テストコードです。最初に Store と Router と Client を Mock しておきます。

Mock 部分

targetClient のレスポンスを下記にように記載する事によってレスポンスに応じたテストを書くこともできます。

jest.mock('@/client/targetClient', () => {
  return () => {
    const resp = {data:{"target": hoge }}
        return {getTarget: () => resp}
})

const mockedPush = jest.fn()

jest.mock('vue-router', () => ({
  useRouter: () => ({
    push: mockedPush,
  }),
}));

jest.mock('vuex', () => ({
  useStore: () => ({
    commit: jest.fn(),
  }),
}));

mockedPush のように push が呼ばれた際のモックメソッドを切り出しているのは下記のようにそれが呼ばれたことを expect で確認するようなテストを書きたいからです。

expect(mockedPush).toBeCalledWith({ name: 'Edit' });

テストケース

下記 2 パターンのテストを作ります。

・onMounted で取得したデータがレンダリングされている事

・ボタンが押されて edit メソッドが呼ばれる事

describe('Target', () => {

  it('画面表示時にtargetを取得してそれが表示される事', async () => {
    const wrapper = shallowMount(Target);
    await sleep(1);
    const  expectedHtml1 = '<p><span>hoge</span>'
    expect(wrapper.html()).toMatch(expectedHtml1)
  })
  
  it('編集ボタンを押下するとeditが実行される事', async () => {
    const wrapper = shallowMount(Target);
    await sleep(1);
    wrapper.find(".edit-button").trigger("click")
    expect(mockedPush).toBeCalledWith({ name: 'Edit' });
  })
})

sleep(1) は下記のようになっていて指定した ms だけまさに sleep させます。

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

何故かこれを入れないと wrapper.vm.target の方は更新されているけれど wrapper.html() が更新されないようでした。。。

原因はいまいちよく分からなかったのですが vm が更新されてからレンダリングされるまで少し待つ必要がある様で一瞬待ってから expect するようにしないとテストが失敗してしまうため仕方なくこうしました。

今回テスト対象コンポーネントの template は省略しましたが、二つ目のテストケースでは edit-button というクラスで対象を指定してクリックすると先ほど書いた mockedPush が呼ばれたかどうか、呼ばれていればこのeditメソッドが正しく呼ばれている、というようなテストを書きました。

まとめ

下記に今回の Target.spec.js の全体を載せておきます。

今回このような形で Vue3 の compositionAPI で useRouter や useStore を使ったコンポーネントのテストを書きましたが、もっとこうした方が良い、等のアドバイスがあれば是非ご教示ください。

import { shallowMount } from '@vue/test-utils';
import Archive from '@/components/pages/Target.vue'

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

jest.mock('@/client/targetClient', () => {
  return () => {
    const resp = {data:{"target": hoge }}
        return {getTarget: () => resp}
})

const mockedPush = jest.fn()

jest.mock('vue-router', () => ({
  useRouter: () => ({
    push: mockedPush,
  }),
}));

jest.mock('vuex', () => ({
  useStore: () => ({
    commit: jest.fn(),
  }),
}));

describe('Target', () => {

  it('画面表示時にtargetを取得してそれが表示される事', async () => {
    const wrapper = shallowMount(Target);
    await sleep(1);
    const  expectedHtml1 = '<p><span>hoge</span>'
    expect(wrapper.html()).toMatch(expectedHtml1)
  })
  
  it('編集ボタンを押下するとeditが実行される事', async () => {
    const wrapper = shallowMount(Target);
    await sleep(1);
    wrapper.find(".edit-button").trigger("click")
    expect(mockedPush).toBeCalledWith({ name: 'Edit' });
  })
})

参考書籍

Vue3 を本で勉強したい方はこちらを参考にしてください。 3 になるとまだあまり種類は出ていない様です。

その他基礎から体系的に勉強したい方はこちらも。