0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Vue3 + Vitest によるUT向けに Composable をモックするための Composable を書く

Last updated at Posted at 2024-02-19

はじめに

vue アプリケーションを作成する場合、状態管理やその関連処理をカプセル化するためにcomposableを使用することは多いと思います。

そのような composable を使用する側ソースのユニットテストを作成する際、毎回 composable をモックするのは無駄が大きいと思ったので、composable をモックするための composable を作成しておいて再利用可能にしようと思いました。

これはその思いつきの記録です。

内容

モック対象の composable を作成する

composableのサンプルにはVue.jsガイドのuseFetchを流用します。

想定するテストを行えるようにするため、今回は、fetchDataがリアクティブで起動しない設計に作り変えておきます。

fetch.ts
import { ref } from 'vue'
import type { Ref } from 'vue'

export const useFetch = (url: Ref<string | undefined>) => {
  const data = ref<string | null>(null)
  const error = ref<string | null>(null)

  const fetchData = async () => {
    if (!url.value) return

    data.value = null
    error.value = null

    await fetch(url.value)
      .then((res) => res.json())
      .then((json) => (data.value = json))
      .catch((err) => (error.value = err.message))
  }

  return { fetchData, data, error }
}

テスト対象ソースを作成する

ここでは、テスト対象ソースはcomponentとし、以下の機能を持つことを想定します。

  • DOMノード生成後に、fetchが実行される
  • ChangeUrlボタンクリック時に、変更されたurlでfetchが実行される
MyComponent.vue
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useFetch } from './fetch'

const url = ref<string>()
const { fetchData, data } = useFetch(url)

const changeUrl = () => {
  url.value = '/new-url'
  fetchData()
}

onMounted(() => {
  url.value = '/initial-url'
  fetchData()
})
</script>

<template>
  <div>
    <div>
      {{ data ?? '' }}
    </div>
    <button @click="changeUrl">
      ChangeUrl
    </button>
  </div>
</template>

モック Composable を作成する

fetch.ts内のメソッドのモック処理を、composableとして用意することで再利用可能とします。
モック後の関数や、状態・投入引数の監視用変数を返却して、テストで利用できるようにしておきます。

fetch.mock.ts
import { ref, watch } from 'vue'
import * as fetch from './fetch'

export const useFetchMock = (dummyData: string | null) => {
  const data = ref<string | null>(null)
  const error = ref<string | null>(null)
  const url = ref<string | undefined>()

  const fetchDataSpy = vi.fn(() => {
    data.value = dummyData
    return Promise.resolve()
  })

  vi.spyOn(fetch, 'useFetch').mockImplementation((param) => {
    watch(param, () => {
      url.value = param.value
    })

    return {
      fetchData: fetchDataSpy,
      data,
      error,
    }
  })

  return {
    fetchDataSpy,
    data,
    url,
  }
}

テストソースを作成する

先に作成した fetch.mock.tsを使用 することで、以下のような観点のテストができるようになりました。

  • fetchDataの起動回数が想定通りであるか
  • fetchDataの起動時のurlが想定通りであるか
import { describe, it } from 'vitest'
import { mount } from '@vue/test-utils'
import { useFetchMock } from './fetch.mock'
import MyComponent from './MyComponent.vue'
import type { VueWrapper } from '@vue/test-utils'

describe('test MyComponent.vue', () => {
  const { fetchDataSpy, url } = useFetchMock('result data') // ここでダミーデータを渡す
  let wrapper: VueWrapper<typeof MyComponent>

  beforeEach(() => {
    wrapper = mount(MyComponent)
  })

  afterEach(() => {
    fetchDataSpy.mockClear()
  })

  it('初期表示時にfetchが実行され、dataが表示される', () => {
    expect(url.value).toBe('/initial-url')
    expect(wrapper.text()).toMatch(/result data/)

    expect(fetchDataSpy).toHaveBeenCalledOnce()
  })

  it('ChangeUrlボタンクリックによって新しいurlでfetchが実行される', async () => {
    const button = wrapper.find('button')
    await button.trigger('click')

    expect(url.value).toBe('/new-url')
    expect(wrapper.text()).toMatch(/result data/)

    expect(fetchDataSpy).toHaveBeenCalledTimes(2) // 計2回
  })
})
0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?