组件测试

为什么要有组件测试?

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

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

效率太低了

初步安装

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

相关推荐
且白19 分钟前
vsCode使用本地低版本node启动配置文件
前端·vue.js·vscode·编辑器
程序研20 分钟前
一、ES6-let声明变量【解刨分析最详细】
前端·javascript·es6
siwangqishiq21 小时前
Vulkan Tutorial 教程翻译(四) 绘制三角形 2.2 呈现
前端
李三岁_foucsli1 小时前
js中消息队列和事件循环到底是怎么个事,宏任务和微任务还存在吗?
前端·chrome
尽欢i1 小时前
HTML5 拖放 API
前端·html
PasserbyX1 小时前
一句话解释JS链式调用
前端·javascript
1024小神1 小时前
tauri项目,如何在rust端读取电脑环境变量
前端·javascript
Nano1 小时前
前端适配方案深度解析:从响应式到自适应设计
前端
古夕1 小时前
如何将异步操作封装为Promise
前端·javascript
小小小小宇1 小时前
前端定高和不定高虚拟列表
前端