如何对 vue3 & Pinia 组件进行单元测试

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

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

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

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

为 Pinia Store 增加单元测试

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

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

代码如下所示:

bash 复制代码
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,并且运行效率更高一些。

测试代码如下:

bash 复制代码
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 实例。

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

对 Vue 组件进行单元测试

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

jsx 复制代码
<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>

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

jsx 复制代码
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 包, 并在我们需要在挂载组件时将其作为插件安装。

jsx 复制代码
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 中的初始数据。

示例代码如下:

jsx 复制代码
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 进行耦合。

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

相关推荐
我是小路路呀6 小时前
element级联选择器:已选中一个二级节点,随后又点击了一个一级节点(仅浏览,未确认选择),此时下拉框失去焦点并关闭
javascript·vue.js·elementui
JIngJaneIL7 小时前
基于springboot + vue古城景区管理系统(源码+数据库+文档)
java·开发语言·前端·数据库·vue.js·spring boot·后端
澄江静如练_7 小时前
列表渲染(v-for)
前端·javascript·vue.js
Loo国昌7 小时前
Vue 3 前端工程化:架构、核心原理与生产实践
前端·vue.js·架构
前端白袍8 小时前
Vue:如何实现一个具有复制功能的文字按钮?
前端·javascript·vue.js
new code Boy9 小时前
escape谨慎使用
前端·javascript·vue.js
爱分享的鱼鱼13 小时前
对比理解 Vue 响应式 API:data(), ref、reactive、computed 与 watch 详解
前端·vue.js
JS_GGbond13 小时前
【性能优化】给Vue应用“瘦身”:让你的网页快如闪电的烹饪秘籍
前端·vue.js
刘一说13 小时前
Vue Router:官方路由解决方案解析
前端·javascript·vue.js
计算机学姐13 小时前
基于php的摄影网站系统
开发语言·vue.js·后端·mysql·php·phpstorm