组件测试

为什么要有组件测试?

新建不同的属性的组件,只能肉眼观测

更新代码,要再次进行手动测试

效率太低了

初步安装

js 复制代码
npm install -D vitest

建一个测试文件:example.test.ts

js 复制代码
import { describe } from 'node:test'
import { expect, test } from 'vitest'

test('test common matcher', () => {
    const name = 'lu'
    expect(name).toBe('lu')
    expect(2 + 2).toBe(4)
    expect(2 + 2).not.toBe(5)
})

test('test to be true or false', () => {
    expect(1).toBeTruthy()
    expect(0).toBeFalsy()
})
test('test numner', () => {
    expect(4).toBeGreaterThan(3)
})
test('test object', () => {
    expect({ name: 'lu' }).toEqual({ name: 'lu' })
})
js 复制代码
npx vitest example

这样就可以显示测试结果:

回调测试、对象方法的调用、第三方库的模拟

js 复制代码
import axios from 'axios'
export function testFn(number: number, callback: Function) {
    if (number > 10) {
        callback(number)
    }
}

export async function request() {
    const { data } = await axios.get('fake.url')
    return data
}
js 复制代码
import { describe } from 'node:test'
import { expect, test, vi, Mocked } from 'vitest'
import { testFn, request } from './utils'
import axios from 'axios'
vi.mock('axios')
const mockAxios = axios as Mocked<typeof axios>
//callback test
describe('function', () => {
    test('create a mock funtion', () => {
        const callback = vi.fn()
        testFn(12, callback)
        expect(callback).toHaveBeenCalled()
        expect(callback).toHaveBeenCalledWith(12)
    })
    test('spy on method', () => {
        const obj = {
            getName: () => 1
        }
        const spy = vi.spyOn(obj, 'getName')
        obj.getName()
        expect(spy).toHaveBeenCalled()
        obj.getName()
        expect(spy).toBeCalledTimes(2)
    })
    test('mock third party module', async () => {
        mockAxios.get.mockImplementation(() => Promise.resolve({ data: 123 }))
        const result = await request()
        expect(result).toBe(123)
    })

})

安装vue-test-utils

js 复制代码
npm install --save-dev @vue/test-utils

挂载Button组件,写Button.test.ts测试文件:

js 复制代码
import { describe, test } from 'vitest'
import Button from './Button.vue'
import { mount } from '@vue/test-utils'

describe('Button.vue', () => {
    test('basic button', () => {
        const wrapper = mount(Button, {
            props: {
                type: 'primary'
            },
            slots: {
                default: 'button'
            }
        })
        console.log(wrapper.html());
    })
})

这里要返回dom,需要配置vitest.confid.ts:

js 复制代码
/// <reference types="vitest"/>

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import vueDevTools from 'vite-plugin-vue-devtools'

// https://vite.dev/config/
export default defineConfig({
    plugins: [
        vue(),
        vueJsx(),
        vueDevTools(),
    ],
    test: {
        globals: true,
        environment: 'jsdom'
    }

})

Button组件:

写基本测试

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

describe('Button.vue', () => {
    test('basic button', () => {
        const wrapper = mount(Button, {
            props: {
                type: 'primary'
            },
            slots: {
                default: 'button'
            }
        })
        console.log(wrapper.html());
        expect(wrapper.classes()).toContain('lu-button--primary')
        //slot 
        //get方法
        //find方法
        expect(wrapper.get('button').text()).toBe('button')
        //event 
        wrapper.get('button').trigger('click')
        console.log(wrapper.emitted());
        expect(wrapper.emitted()).toHaveProperty('click')


    })

    test('disabled', () => {
        const wrapper = mount(Button, {
            props: {
                disabled: true
            },
            slots: {
                default: 'disabled'
            }
        })
        expect(wrapper.attributes('disabled')).toBeDefined()
        expect(wrapper.find('button').element.disabled).toBeDefined()
        expect(wrapper.emitted()).not.toHaveProperty('click')
    })
})

测试和图标相关的内容

subs替换功能:

因为图标的实现比较复杂所以使用subs,在测试 Button 组件时,使用 stubs 替换 FontAwesomeIcon 组件后,测试就可以专注于 Button 组件自身的逻辑和渲染,而不用考虑 FontAwesomeIcon 组件的行为

js 复制代码
test('icon', () => {
        const wrapper = mount(Button, {
            props: {
                icon: 'arrow-up'
            },
            slots: {
                default: 'icon'
            },
            global: {
                stubs: ['FontAwesomeIcon']
            }
        })
        console.log(wrapper.html());
        const iconElement = wrapper.findComponent(FontAwesomeIcon)
        expect(iconElement.exists()).toBe(true)
        expect(iconElement.attributes('icon')).toBe('arrow-up')

    })
    test('loading', () => {
        const wrapper = mount(Button, {
            props: {
                loading: true
            },
            slots: {
                default: 'loading'
            },
            global: {
                stubs: ['Icon']
            }
        })
        console.log(wrapper.html());
        const iconElement = wrapper.findComponent(Icon)
        expect(iconElement.exists()).toBe(true)
        expect(iconElement.attributes('icon')).toBe('spinner')
        expect(iconElement.attributes('disables')).toBeDefined
    })

Collapse组件:

js 复制代码
import { describe, expect, test } from 'vitest';
import { mount } from '@vue/test-utils';
import Collapse from './Collapse.vue';
import CollapseItem from './CollapseItem.vue';

describe('Collapse.vue', () => {
    test('basic collapse', async () => {
        const wrapper = mount(() => (
            <Collapse modelValue={['a']}>
                <CollapseItem name="a" title="title a">
                    content a
                </CollapseItem>
                <CollapseItem name="b" title="title b">
                    content b
                </CollapseItem>
                <CollapseItem name="c" title="title c" disabled>
                    content c
                </CollapseItem>
            </Collapse>
        ), {
            global: {
                stubs: ['Icon']
            },
            attachTo: document.body
        });

        //是否会渲染出结构
        const headers = wrapper.findAll('.lu-collapse-item__header')
        const contents = wrapper.findAll('.lu-collapse-item__content')

        expect(headers.length).toBe(3)
        expect(contents.length).toBe(3)

        //文本
        const firstHeader = headers[0]
        expect(firstHeader.text()).toBe('title a')

        //内容
        const firstContent = contents[0]
        const secondContent = contents[1]
        expect(firstContent.isVisible).toBeTruthy()
        expect(secondContent.isVisible).toBeTruthy()

        //点击
        firstHeader.trigger('click');
        await wrapper.vm.$nextTick();
        expect(firstContent.isVisible()).toBe(false);
    });
});

使用 await wrapper.vm.$nextTick() 后,代码会暂停执行,直到 DOM 更新完成。之后再执行 expect(firstContent.isVisible()).toBe(false) 这一断言,就能确保此时获取到的 firstContent 的显示状态是最新的,这样的断言结果才是准确可靠的。

相关推荐
QQ1__8115175152 小时前
Spring boot名城小区物业管理系统信息管理系统源码-SpringBoot后端+Vue前端+MySQL【可直接运行】
前端·vue.js·spring boot
钛态2 小时前
前端微前端架构:大项目的救命稻草还是自找麻烦?
前端·vue·react·web
一粒黑子2 小时前
【实战解析】阿里开源 PageAgent:纯前端 GUI Agent,一行JS让网页支持自然语言操控
前端·javascript·开源
独角鲸网络安全实验室2 小时前
2026微信小程序抓包全解析:从实操落地到合规风控,解锁前端调试新范式
前端·微信小程序·小程序·抓包·系统代理绕过·https证书严格校验·进程隔离
紫微AI2 小时前
前端文本测量成了卡死一切创新的最后瓶颈,pretext实现突破了
前端·人工智能·typescript
GISer_Jing2 小时前
AI前端(From豆包)
前端·aigc·ai编程
IT枫斗者2 小时前
前端部署后如何判断“页面是不是最新”?一套可落地的版本检测方案(适配 Vite/Vue/React/任意 SPA)
前端·javascript·vue.js·react.js·架构·bug
测试修炼手册2 小时前
[测试技术] 深入理解 JSON Web Token (JWT)
前端·json
AI老李2 小时前
2026 年 Web 前端开发的 8 个趋势!
前端
里欧跑得慢2 小时前
15. Web可访问性最佳实践:让每个用户都能平等访问
前端·css·flutter·web