组件测试

为什么要有组件测试?

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

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

效率太低了

初步安装

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 的显示状态是最新的,这样的断言结果才是准确可靠的。

相关推荐
向日葵花籽儿6 分钟前
#运维 | 前端 # Linux http.server 实践:隐藏长文件名,简短路径 (http://IP:port/别名 ) 访问
linux·运维·前端
zheshiyangyang19 分钟前
uni-app学习【pages】
前端·学习·uni-app
nightunderblackcat1 小时前
新手向:异步编程入门asyncio最佳实践
前端·数据库·python
前端工作日常1 小时前
我的 Jasmine 入门之旅
前端·单元测试·测试
前端小巷子1 小时前
Vue 3 运行机制
前端·vue.js·面试
奋斗的小羊羊9 小时前
HTML5关键知识点之多种视频编码工具的使用方法
前端·音视频·html5
前端呆猿9 小时前
深入解析HTML5中的object-fit属性
前端·css·html5
再学一点就睡9 小时前
实现大文件上传全流程详解(补偿版本)
前端·javascript·面试
你的人类朋友10 小时前
【Node&Vue】什么是ECMAScript?
前端·javascript·后端
路灯下的光10 小时前
用scss设计一下系统主题有什么方案吗
前端·css·scss