Vue.Js单元测试综合指南

Vue.Js单元测试综合指南

网络开发中的测试对于确保网络应用程序的可靠性、功能性和质量至关重要。通过系统地检查功能、性能和用户体验等各个方面,测试有助于识别和纠正错误、缺陷或意外行为。它能提高用户对应用程序的整体满意度和信任度,并通过在开发过程中尽早发现问题来节省时间和金钱。本文为更深入地探讨单元测试在开发稳健可靠的基于网络的解决方案中发挥的关键作用奠定了基础。

Vue.js 中的单元测试需要使用 VitestVue Test UtilsJest 等关键工具,以确保测试的彻底性和可靠性。Vitest 是 Vue.js 的官方实用程序库,它可以模拟 Vue 组件,并对组件进行隔离测试。首先,在终端中键入 Vue CLI 命令,创建一个新的 Vue 项目。

npm create vue@latest

然后,系统会提示您选择应用程序所需的所有依赖项。

应用程序所需的所有依赖项

接下来,选择 Vue RouterPinia、Vitest,然后按回车键。你也可以克隆这个 boilerplate。这里已经添加了所需的一切。

编写第一个 Vue.js 单元测试

在本示例中,我们将在 src/components/ 中创建一个名为 BaseButton.vue 的按钮组件。该按钮接受文本 prop,点击后会向用户显示新文本。

<template>
<button @click="handleButton">
{{ text }}
</button>
<p>{{ textToRender }}</p>
</template>
<script>
import { ref } from 'vue'
export default {
props: {
text: String
},
setup() {
const textToRender = ref('default')
const handleButton = () => {
textToRender.value = 'Hi there'
}
return {
handleButton,
textToRender
}
}
}
</script>

点击后会向用户显示新文本

我们将为该组件的 propsstate 和 user 交互创建一个基本的穿行测试。接下来,在你的测试文件夹中创建一个扩展名为”.test.js” 的文件,然后按照下面的示例实现你的第一个测试。

import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import BaseButton from 'from-your-component-location'
describe('MyButton.vue', () => {
it('renders button text from props', () => {
const text = 'Click Me'
const wrapper = mount(BaseButton, {
props: { text }
})
expect(wrapper.text()).toMatch(text)
})
})

在本示例中,为 BaseButton.vue 组件创建了一个测试套件。该套件中的一个测试用例将验证按钮文本是否使用所提供的道具正确呈现。@vue/test-utils 中的 mount 函数用于挂载组件进行测试。测试使用 Vitest 的语法运行,其中包括测试套件创建函数(如 describeit )以及断言函数(如 expect ),以便在测试过程中验证特定的组件行为。接下来,我们将编写一个测试来模拟用户点击按钮时对用户界面的更新。

it('updates text when button is clicked', async () => {
const wrapper = mount(BaseButton, {
props: { text: 'Initial' }
})
await wrapper.find('button').trigger('click')
expect(wrapper.find('p').text()).toBe('Hi there')
})

在该测试中使用了一个异步函数,通过触发对挂载组件中按钮元素的点击来模拟按钮点击事件。在这种交互之后,测试希望组件 p 标签中的内容是 “Hi there”,从而验证按钮点击动作后的预期文本更新。这个异步测试确保了组件能正确响应用户交互,在点击按钮时更改显示的文本。接下来,我们还将编写一个测试来模拟状态。

it('verifies default state', () => {
const wrapper = mount(BaseButton)
expect(wrapper.find('p').text()).toBe('default')
})

此示例通过在没有任何初始道具的情况下安装按钮组件,并确保 p 标签中的文本显示 “default”,来验证按钮组件的默认状态。这将确保组件在任何道具初始化之前都能按照预期的方式运行。现在您已完成测试套件,请在终端上运行此命令。如果一切顺利,测试就会通过。

npm run test:unit

显示测试已成功通过

上图显示测试已成功通过。组件行为测试取决于你是否知道如何使用断言和期望来验证和确保获得预期结果。它们涉及创建有关组件功能预期结果的具体声明。以下是您应该了解的断言和期望:

  • 断言用于验证条件或预期结果。
  • 期望则为假设为真设定了准则。断言的例子有 toEqualtoContaintoBeDefined等。

模拟依赖关系和应用程序接口

模拟是单元测试中的一项重要技术,因为它允许开发人员模拟组件所依赖的外部依赖关系、函数或服务。当依赖关系复杂、涉及网络请求或不直接受测试环境控制时,模拟就显得至关重要。模拟创建这些依赖关系的受控实例,允许开发人员在测试过程中预测它们的行为、响应和结果,而无需执行真实逻辑或依赖真实的外部系统。假设你有一个从外部 API 获取数据的函数 getDataFromAPI

export const getDataFromAPI = () => {
//Returns the response data;
}

在主组件中,您可以使用 getDataFromAPI 这个函数

import { getDataFromAPI } from './eventService'
export async function processDataFromAPI() {
try {
const data = await getDataFromAPI()
return data
} catch (e) {
//handle error;
}
}

要测试 processDataFromAPI,可以模拟 getDataFromAPI 函数。

import { expect, vi, test } from 'vitest'
import { processDataFromAPI } from './mainComponent'
import * as eventService from '../eventService'
vi.mock('./eventService.js')
test('processDataFromAPI function test', async () => {
const mockData = 'Mocked Data'
eventService.getDataFromAPI.mockResolvedValue(mockData)
const result = await processDataFromAPI()
expect(eventService.getDataFromAPI).toHaveBeenCalled()
expect(result).toBe('Mocked Data')
})

在本示例中,代码演示了如何使用 mock 来验证服务函数 processDataFromAPI 的功能。 vi.mock 函数用 eventService 的模拟实现替换了实际的 getDataFromAPI 调用。当指定 eventService.getDataFromAPI.mockResolvedValue(mockData) 时,被模拟的函数将使用设置值 “Mocked Data” 进行解析。以下是测试结果:

验证对模拟数据的处理

下面的测试将检查是否调用了模拟的 getDataFromAPI,并验证对模拟数据的处理,从而确保 processDataFromAPI 的正确运行。该测试可有效隔离并检查 processDataFromAPI 的行为,而无需依赖实际的 API 调用来获取数据。

高级测试技术

在本节中,我们将讨论如何在应用程序的某个关键部分利用测试。您将了解到应用于 Pinia Store 和 Vue 路由的各种测试方法。测试 Pinia 商店的主要目的是评估状态和操作的行为和功能。调查的关键领域包括确保状态变化准确反映商店中的操作。因此,测试要确认操作能准确执行对商店状态的突变和逻辑操作。

import { defineStore } from 'pinia'
export const useCounterStore = defineStore({
id: 'counter',
state: () => ({
count: 0
}),
actions: {
increment() {
this.count++
},
decrement() {
this.count--
}
}
})

在这个示例中,存储为计数器定义了一个简单的状态,并通过相应的操作来递增和递减计数。现在让我们创建一个 CounterStore.spec.js

import { setActivePinia, createPinia } from 'pinia'
import { expect, describe, it, beforeEach } from 'vitest'
import { useCounterStore } from '../../stores/counter'
describe('Counter Store', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
it('increments the count', () => {
const counterStore = useCounterStore()
counterStore.increment()
expect(counterStore.count).toBe(1)
})
it('decrements the count', () => {
const counterStore = useCounterStore()
counterStore.decrement()
expect(counterStore.count).toBe(-1)
})
it('increments the count twice', () => {
const counterStore = useCounterStore()
counterStore.increment()
counterStore.increment()
expect(counterStore.count).toBe(2)
})
})

这段代码设置了用于测试的 Vue 路由,并定义了一个简单的路由保护逻辑。路由只有一个路由 /about,其 requiresAuth 元字段设置为 true。路由保护函数 beforeEach 会检查路由是否需要授权。出于演示目的,它模拟了一次身份验证检查; isAuthenticated 设置为 false,将未通过身份验证的用户重定向回主路由 '/' ,同时允许访问不需要授权的路由。测试套件包括两个测试,一个是确保授权后访问受保护路由,另一个是验证未经授权的用户是否被拒绝访问受保护路由,模拟路由保护的行为。根据应用程序的身份验证逻辑调整 isAuthenticated 条件。这是一张显示测试结果的图片。运行测试,应该会得到相同的结果。

显示测试结果的图片

接下来,我们将学习如何测试 Vue 路由。在测试 Vue 路由导航和路由保护时,确保导航行为和路由保护机制按预期运行至关重要。使用 Vitest 这样的测试库,您可以模拟路由操作并验证结果。下面是测试导航和路由保护的示例代码。

import { expect, it, describe } from 'vitest'
import { mount } from '@vue/test-utils'
import { createRouter, createWebHistory } from 'vue-router'
import AboutView from '../../views/AboutView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/about',
name: 'about',
component: AboutView,
meta: { requiresAuth: true }
}
]
})
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth) {
const isAuthenticated = false
if (isAuthenticated) {
next()
} else {
next('/')
}
} else {
next()
}
})
describe('Vue Router Navigation', () => {
it('navigates to a protected route with proper authorization', async () => {
await router.push('/about')
await router.isReady()
const wrapper = mount(AboutView, {
global: {
plugins: [router]
}
})
expect(wrapper.findComponent(AboutView).exists()).toBe(true)
})
it('prevents access to a protected route without authorization', async () => {
await router.push('/about')
expect(router.currentRoute.value.fullPath).not.toBe('/about')
})
})

这段代码设置了用于测试的 Vue 路由,并定义了一个简单的路由保护逻辑。路由只有一个路由 /about,其 requiresAuth 元字段设置为 true。路由保护函数 beforeEach 会检查路由是否需要授权。出于演示目的,它模拟了一次身份验证检查; isAuthenticated 设置为 false ,将未通过身份验证的用户重定向回主路由 '/' ,同时允许访问不需要授权的路由。测试套件包括两个测试,一个是确保授权后访问受保护路由,另一个是验证未经授权的用户是否被拒绝访问受保护路由,模拟路由保护的行为。根据应用程序的身份验证逻辑调整 isAuthenticated 条件。这是一张显示测试结果的图片。运行测试,应该会得到相同的结果。

一张显示测试结果的图片

测试覆盖率

测试覆盖率是软件开发中的一个重要指标,因为它衡量了测试套件对代码库的彻底检查程度。它表明测试使用了多少代码。提高测试覆盖率可确保代码的更多部分得到验证,从而降低未检测到错误的可能性。它还有助于识别代码中缺乏测试覆盖率的区域,使开发人员能够将测试工作集中在这些关键部分。Vitest 自带原生覆盖率支持,只需几步就能获得代码库的覆盖率报告。在终端上运行此命令,为测试添加覆盖率。

npm i -D @vitest/coverage-v8

完成安装后,将其复制到您的终端,您将得到一份覆盖范围报告。

npm run coverage

覆盖率报告

该覆盖率报告概述了代码库中的测试覆盖范围,提供了关键指标:%Stmts、%Branch、%Funcs 和 %Lines。这些百分比表示测试覆盖的代码、决策分支、函数和行数。 HelloWorld.vueAboutView.vue 等文件具有完整的覆盖率,而 eventService.jsmainComponent.js 则显示了部分覆盖率,尤其是在特定行中,突出了需要改进的地方。本报告可作为优先测试工作的指南,引导人们关注未覆盖的部分,以进行更全面、更可靠的测试。

小结

总之,前端应用程序测试对于确保代码的可靠性、稳定性和正确性至关重要。它能及早发现错误,保持代码的高质量,并促进可扩展性。团队通过实施日常测试来确保软件的稳健性和可维护性,从而获得更好的用户体验和整体软件产品。

评论留言