头图

Pinia 是 Vue 3 领域无可争议的状态管理冠军。

它提供了更强大和可扩展的架构,并具有遵循组合 API 语法的优雅代码风格。

入门很简单,特别是如果您是Vue2 + Vuex 开发者,但单元测试中的一些细微差异可能会让您措手不及。

让我们探索一些可能在未来节省您时间的现实生活场景。

为 Pinia Store 增加单元测试

在我们深入进行组件测试之前,让我们先探讨一下如何单独测试一个简单的存储。

为此我们使用 vue cli 创建简单的模板工程,用 计数器 store 做一个演示。

代码如下所示:

import { ref, computed } from 'vue'
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)

  const doubleCount = computed(() => count.value * 2)

  function increment(amount = 1) {
    count.value += amount
  }

  return { count, doubleCount, increment }
})

如果你是第一次使用 Pinia , 那以下几个建议会对你有帮助:

  • Vue 中的 方法, 将变为 store 里的 actions
  • Vue 中的 ref 变量 和 reactive 变量 等同于 store 中的 state
  • Vue 中的 computed 属性将等同于 store 中的 getters

增加单元测试很简单:

我们只需要在store 下面 增加目录 test

并创建 counter.spec.js 单元测试文件。

注意:

我们这里使用的单元测试框架是 vittest . 这是一个 vite支持测试运行的框架,具有和jest 完全一样的API,并且运行效率更高一些。

测试代码如下:

import { setActivePinia, createPinia } from 'pinia'
import { useCounterStore } from '../counter'
import { beforeEach, describe, it, expect } from 'vitest'

describe('Counter Store', () => {

    beforeEach(() => {
        setActivePinia(createPinia())
    })

    it('increment with no parameters should add one to the counter', () => {
        const counter = useCounterStore()
        expect(counter.count).toBe(0)
        counter.increment()
        expect(counter.count).toBe(1)
    })

    it('doubleCount getter should be double the counter at all times', () => {
    const counter = useCounterStore()
        expect(counter.doubleCount).toBe(0)
        counter.increment()
        expect(counter.count).toBe(1)
        expect(counter.doubleCount).toBe(2)
    })
})

在 beforeEach 钩子中,创建并激活了一个 pinia 实例。

没有它,商店就无法工作,如果我们省略它,将会抛出以下错误:

Untitled.jpeg

对 Vue 组件进行单元测试

我们用计数器Store 简单实现一个,计数器的组件。 代码如下:

<script setup>
import { useCounterStore } from '../stores/counter'
const store = useCounterStore()
</script>

<template>
    <div class="counterWrap">
        <h1>
            计数器:
        </h1>
        <p class="count">
            当前值:<span> {{  store.count }}</span>
        </p>
        <p class="double-count">
            2倍值: <span> {{ store.doubleCount }} </span>
        </p>
        <button @click="store.increment">
            递增加一
        </button>
    </div>
</template>

因为该组件功能和代码都比较简单,所以实现对该组件的单元测试也比较简单。

import { mount } from "@vue/test-utils";
import Counter from '../Counter.vue'
import { setActivePinia, createPinia } from "pinia";
import { beforeEach, describe, test, expect } from "vitest";

describe('Counter Component', () => {
    beforeEach(() => {
        setActivePinia(createPinia())
    })

    test('计数器初始值应该为 0', async () => {
        const wrapper = mount(Counter)
        expect(wrapper.find('.count span').text()).toBe('0')
    })

    test('按钮点击,计数器的值应该为 1', async () => {
        const wrapper = mount(Counter)
        await wrapper.find('button').trigger('click')
        expect(wrapper.find('.count span').text()).toBe('1')
    })

    test('2倍值显示的数应该为当前值 乘 2', async () => {
        const wrapper = mount(Counter)
        expect(wrapper.find('.count span').text()).toBe('0')
        expect(wrapper.find('.double-count span').text()).toBe('0')
        await wrapper.find('button').trigger('click')
        expect(wrapper.find('.count span').text()).toBe('1')
        expect(wrapper.find('.double-count span').text()).toBe('2')
    })
})

值得注意的是, 我们在beforeEach 生命周期中, 再次的创建并激活了 Pinia 实例。

至此,我们的Vue3 组件测试就基本跑通了, 但是存在一个不太明显小问题。

我们正在使用实际的 useCounterStore 实现,并且我们不会单独测试 Counter 组件。

要解决这个问题也不难。 我们需要 安装@pinia/testing 包(npm i -D @pinia/testing)。 这个包提供了 Moc 功能,让我们可以独立测试组件的功能。

首先我们需要安装 npm 包, 并在我们需要在挂载组件时将其作为插件安装。

import { mount } from "@vue/test-utils";
import Counter from '../Counter.vue'
import { describe, test, expect, vi } from "vitest";
import { createTestingPinia } from '@pinia/testing'

describe('Counter Mock 测试', () => {
    test('the count initially should be 0', async () => {
        const wrapper = mount(Counter, {
          global: {
            plugins: [
              createTestingPinia({
                createSpy: vi.fn,
              })
            ]
          }
        })
        expect(wrapper.find('.count span').text()).toBe('0')
      })
})

当然,我们也可以通过 initialState 属性,来配置 mock Store 中的初始数据。

示例代码如下:

import { mount } from "@vue/test-utils";
import Counter from '../Counter.vue'
import { describe, test, expect, vi } from "vitest";
import { createTestingPinia } from '@pinia/testing'

describe('Counter Mock 测试', () => {
    test('the count initially should be 0', async () => {
        const wrapper = mount(Counter, {
          global: {
            plugins: [
              createTestingPinia({
                createSpy: vi.fn,
                initialState: {
                  counter: {
                    count: 20
                  }
                }
              })
            ]
          }
        })
        expect(wrapper.find('.count span').text()).toBe('20')
      })
})

默认情况下,当我们使用了 createTestingPinia 之后,所有 Store 中的 action 都会被模拟并且不会执行真实Store 中的代码。 我们需要在 createTestingPinia 的方法中,进行模拟action 函数实现。

总结

我们在以上代码中以计数器为例,演示了,如何用vitest 对Vue3 组件和 Pinia Store 进行单元测试。并演示,如何通过 createTestingPinia 对Vue3 组件进行单独的组件逻辑测试而不和Store 进行耦合。

但是,实际开发场景中单元测试的复杂度要远大于目前简单的计数器组件。 我会在未来几天更新一下,牵扯到异步请求的代码如何进行单元测试。 敬请期待吧。


我是老石,一个教你做全栈的前端架构师。


Sean
26 声望2 粉丝

一个 有趣的老程序员