vitest单元测试笔记

介绍

之前的文章已经做好了安装,然后我们需要配置测试模块。这里就涉及到如何取写配置。如何测试。比如页面是否正常渲染,是否含有对应的模块。按钮点击之后页面的变化,输入文本框内容的变化等等。经过测试发现复杂一点的页面完全不能测,写测试案例的成本比写代码的成本还高,哪怕是智能体帮你写。建议用puppeteer来测,也就是无头浏览器。

教程

基础模版

代码如下:

复制代码
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import TestView from './index.vue'

/**
 * 测试TestView组件的功能
 * 该测试套件包含对组件渲染、交互功能的全面测试
 */
describe('TestView', () => {
  /**
   * 测试组件是否正确渲染
   * 验证页面中关键文本内容是否存在
   */
  it('renders correctly', () => {
    // 挂载TestView组件
    const wrapper = mount(TestView)
    
    // 验证页面包含预期的关键文本
    expect(wrapper.text()).toContain('自动化测试专用页面')
    expect(wrapper.text()).toContain('测试组件')
    expect(wrapper.text()).toContain('表单测试')
  })
})

describe创建测试,所有的测试列都在第二个回调参数里写。

it编写测试列,参数1为名称(名称可以随意编写),参数2为回调。

wrapper 挂载组件进来。

wrapper.text() 获取文档内容。

expect(wrapper.text()).toContain('自动化测试专用页面') 判断文档里是否含有这几个字。

运行日志

如下代码则表示 Login Component 表示上面的describe函数的第一个参数。

renders login form correctly则为it里的第一个参数。

FAIL src/views/login.test.js > Login Component > renders login form correctly

FAIL src/views/login.test.js > Login Component > should validate required fields

FAIL src/views/login.test.js > Login Component > should call login function when form is submitted with valid data

FAIL src/views/login.test.js > Login Component > should handle login error correctly

FAIL src/views/login.test.js > Login Component > should set cookies when rememberMe is checked

FAIL src/views/login.test.js > Login Component > should remove cookies when rememberMe is unchecked

验证函数

复制代码
const wrapper = mount(TestView)
expect(wrapper.text()).toContain('点击次数: 0')

验证页面内容中是否含有此文字内容。

验证函数toContain与toBe的区别

  1. toBe 匹配器

    • 用于精确匹配 ,验证两个值是否完全相等(使用 Object.is 进行比较)
    • 适用于基本数据类型的完全匹配,如数字、字符串等
    • 要求实际值与期望值完全一致
  2. toContain 匹配器

    • 用于包含匹配,验证一个字符串是否包含指定的子字符串
    • 适用于检查文本内容中是否包含特定内容
    • 只要实际值中包含期望值即可,不要求完全一致

触发按钮点击

复制代码
const buttons = wrapper.findAll('button')
const clickButton = buttons[0] // "点击测试" button
await clickButton.trigger('click')
expect(wrapper.text()).toContain('点击次数: 1')

等待点击之后,再验证一下页面内容的变化,是否含有改文字内容。

测试文本框输入

复制代码
const input = wrapper.findAll('input').at(0) // 第一个input是测试输入框
// 设置输入框值并验证更新
await input.setValue('测试输入')
expect(input.element.value).toBe('测试输入')

案例

登录测试文件,真的是太复杂了,各种报错。根本就没法进行,测试代码比我业务代码还多,问题也比业务代码还多。这个用vue组件来测试确实是太鸡肋了。完全是拖后腿。

复制代码
import { mount } from '@vue/test-utils'
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { nextTick } from 'vue'
import Cookies from 'js-cookie'
import login from './login.vue'
import useUserStore from '@/store/modules/user'
import { setPassword } from '@/utils/password'

// Mock 依赖
vi.mock('@/api/login', () => ({
  getCodeImg: vi.fn()
}))

vi.mock('@/utils/jsencrypt', () => ({
  encrypt: vi.fn((val) => val),
  decrypt: vi.fn((val) => val)
}))

vi.mock('@/utils/password', () => ({
  setPassword: vi.fn((val) => val)
}))

vi.mock('@/store/modules/user', () => {
  const mockLoginFn = vi.fn()
  return {
    default: vi.fn(() => ({
      login: mockLoginFn,
      token: ''
    })),
    mockLoginFn
  }
})

// Mock window.location.reload
const mockReload = vi.fn()
Object.defineProperty(window, 'location', {
  configurable: true,
  value: { reload: mockReload }
})

describe('Login Component', () => {
  let wrapper
  
  const mockPush = vi.fn()
  const mockRouter = {
    push: mockPush
  }

  beforeEach(() => {
    vi.clearAllMocks()
    
    // 创建组件实例
    wrapper = mount(login, {
      global: {
        mocks: {
          $router: mockRouter
        },
        provide: {
          router: mockRouter
        },
        stubs: {
          'svg-icon': {
            template: '<span data-test="svg-icon" />'
          },
          'el-form': {
            template: '<form><slot /></form>',
            props: ['model', 'rules', 'ref']
          },
          'el-form-item': {
            template: '<div><slot /></div>',
            props: ['prop']
          },
          'el-input': {
            template: '<input :value="modelValue" @input="$emit(\'update:modelValue\', $event.target.value)" />',
            props: ['modelValue', 'placeholder', 'type', 'size']
          },
          'el-button': {
            template: '<button><slot /></button>',
            props: ['loading', 'type', 'size']
          },
          'el-checkbox': {
            template: '<div><input type="checkbox" :checked="modelValue" @change="$emit(\'update:modelValue\', $event.target.checked)" /> <slot /></div>',
            props: ['modelValue']
          },
          'router-link': {
            template: '<a><slot /></a>',
            props: ['to']
          }
        }
      }
    })
  })

  it('renders login form correctly', () => {
    expect(wrapper.find('.login-form').exists()).toBe(true)
    expect(wrapper.find('#idgesRa3').exists()).toBe(true)
    expect(wrapper.find('#idAlETvN').exists()).toBe(true)
    expect(wrapper.find('#id5OXvv3').exists()).toBe(true)
    expect(wrapper.find('#idLGUsOP').exists()).toBe(true)
  })

  it('should validate required fields', async () => {
    const button = wrapper.find('#idLGUsOP')
    await button.trigger('click')
    
    // 检查 validate 方法是否被调用
    expect(wrapper.vm.$refs.loginRef.validate).toHaveBeenCalled()
  })

  it('should call login function when form is submitted with valid data', async () => {
    const userStore = useUserStore()
    userStore.login.mockResolvedValueOnce()
    
    const usernameInput = wrapper.find('#idgesRa3')
    const passwordInput = wrapper.find('#idAlETvN')
    const loginButton = wrapper.find('#idLGUsOP')
    
    await usernameInput.setValue('testuser')
    await passwordInput.setValue('testpass')
    await loginButton.trigger('click')
    
    await nextTick()
    
    // 检查是否调用了密码加密函数
    expect(setPassword).toHaveBeenCalled()
    
    // 检查是否调用了登录函数
    expect(userStore.login).toHaveBeenCalled()
  })

  it('should handle login error correctly', async () => {
    const userStore = useUserStore()
    userStore.login.mockRejectedValueOnce(new Error('Login failed'))
    
    const usernameInput = wrapper.find('#idgesRa3')
    const passwordInput = wrapper.find('#idAlETvN')
    const loginButton = wrapper.find('#idLGUsOP')
    
    await usernameInput.setValue('testuser')
    await passwordInput.setValue('testpass')
    await loginButton.trigger('click')
    
    await nextTick()
    
    // 检查错误处理逻辑
    expect(userStore.login).toHaveBeenCalled()
    // 检查 validate 方法是否被调用
    expect(wrapper.vm.$refs.loginRef.validate).toHaveBeenCalled()
  })

  it('should set cookies when rememberMe is checked', async () => {
    const setSpy = vi.spyOn(Cookies, 'set')
    
    const usernameInput = wrapper.find('#idgesRa3')
    const passwordInput = wrapper.find('#idAlETvN')
    const rememberCheckbox = wrapper.find('#idwEZhKM input')
    const loginButton = wrapper.find('#idLGUsOP')
    
    await usernameInput.setValue('testuser')
    await passwordInput.setValue('testpass')
    await rememberCheckbox.setChecked(true)
    await loginButton.trigger('click')
    
    await nextTick()
    
    expect(setSpy).toHaveBeenCalledWith('username', 'testuser', { expires: 30 })
    expect(setSpy).toHaveBeenCalledWith('password', 'testpass', { expires: 30 })
    expect(setSpy).toHaveBeenCalledWith('rememberMe', true, { expires: 30 })
  })

  it('should remove cookies when rememberMe is unchecked', async () => {
    const removeSpy = vi.spyOn(Cookies, 'remove')
    
    const rememberCheckbox = wrapper.find('#idwEZhKM input')
    await rememberCheckbox.setChecked(false)
    
    const loginButton = wrapper.find('#idLGUsOP')
    await loginButton.trigger('click')
    
    await nextTick()
    
    expect(removeSpy).toHaveBeenCalledWith('username')
    expect(removeSpy).toHaveBeenCalledWith('password')
    expect(removeSpy).toHaveBeenCalledWith('rememberMe')
  })
})
相关推荐
wdfk_prog22 分钟前
[Linux]学习笔记系列 -- [kernel]workqueue
linux·笔记·学习
wdfk_prog23 分钟前
[Linux]学习笔记系列 -- [kernel]usermode_helper
linux·笔记·学习
一只爱做笔记的码农1 小时前
【BootstrapBlazor】移植BootstrapBlazor VS工程到Vscode工程,报error blazor106的问题
笔记·学习·c#
木西1 小时前
人间观察:关于健康、善良与选择的思考
笔记
星轨初途1 小时前
数据结构排序算法详解(2)——选择排序(附动图)
c语言·数据结构·经验分享·笔记·b树·算法·排序算法
charlie1145141913 小时前
勇闯前后端Week2:后端基础——Flask API速览
笔记·后端·python·学习·flask·教程
深蓝海拓3 小时前
OpenCV学习笔记之:调整ORB算法的参数以适应不同的图像
笔记·opencv·学习
d111111111d3 小时前
STM32外设--SPI读取W25Q64(学习笔记)硬件SPI
笔记·stm32·单片机·嵌入式硬件·学习
摇滚侠3 小时前
2025最新 SpringCloud 教程,Nacos-配置中心-数据隔离-动态切换环境,笔记18
java·笔记·spring cloud
_Kayo_4 小时前
vue3 computed 练习笔记
前端·vue.js·笔记