测试工具

vitest

测试实例

import { type VueWrapper, mount } from '@vue/test-utils'
import {afterAll, beforeAll, describe, expect, test } from 'vitest'
import operateBar from '../../operateBar.vue'
import type { ComponentPublicInstance } from 'vue'

describe('模块名', () => {
    let wrapper: VueWrapper<ComponentPublicInstance>

    beforeAll(() => {
        wrapper = mount(operateBar, {
            props: {},
            emits: {},
            global: {
                components: {
                    'el-input': ElInput // 全局注册组件
                },
                plugins: [VueI18n], // app.use(vue-i18n)
                directives: {},
            }
        })
    })

    afterAll(() => {
        wrapper && wrapper.unmount()
    })

    test('测试用例1', async () => {
        wrapper.exists() // 是否存在
        wrapper.vm     // 组件实例
        wrapper.vm.$emit('foo') // 抛出组件上的事件
        wrapper.vm.xxx() // 执行组件上的方法
        element.trigger('click') // 点击
        wrapper.emitted().action.toBeTruthy() 
        wrapper.emitted().foo // 返回一个数组,返回的数组中每一项是执行一次 emit 的返回结果,如返回:[['abc'], ['def']],这是执行了2次 emit 的结果
        wrapper.emitted().action // .toEqual([['copy']])
        const header = wrapper.findComponent(Header) // 从  wrapper 中查找子组件 Header

        const element = wrapper.find('.xxx') // 获取元素
        const element = wrapper.findAll('[data-test="xxx"]').at(0) // 获取元素


        expect(element).toBe(xxx)
        expect(element).toEqual(xxx) // 可以对比数组
        expect(element).toMatch(msg)
        expect(element).toBeFalsy()
        expect(element).toBeTruthy() // 是否为 true
    })
})

JEST

异步函数的测试

回调形式的异步函数

使用 done() 函数,done调用之前不会结束测试

test('回调测试异步', (done) => {
    fetchData(res => {
        expect(res.data.data.a).toBe(2)
        done()
    })
})

Promise 形式的异步函数

test(' promise 测试异步', () => {
    return fetchData().then(res => {
        expect(res.data.data.a).toBe(2)
    })
})

对于 catch 的捕获测试,需要借助 expect.asertions() 函数,参数表示执行几次 expect 之后结束:

test(' catch 测试异步', () => {
    expect.asertions(1)
    return fetchData().catch(e => {
        expect(e.toString().includes('404')).toBe(true)
    })
})

其他异步测试的方法

// async await 代替 return
test('其他异步测试方法', async () => {
    await expect(fetchData()).resoves.toMatchObject({
        data: { code: 0 }
    })
})

test('其他异步测试方法', async() => {
    await expect(fetchData()).rejects.toThrow()
})

Jest 中的钩子函数

beforeAll() // 所有用例执行之前初始化

beforeAll(() => {
    counter = new Counter();
})
afterAll() // 所有用例执行之后

beforeEach() // 每一个用例执行之前

afterEach() // 每一个用例执行之后 

单个用例调试

test.only('', () => {})

Mock

mock 一个函数,可以捕获函数的调用

const func = jest.fn() // 生成函数
runCallback(func) // 执行
runCallback(func)
expect(func).toBeCalled()
// expect(func.mock.results.length).toBe(2)
// func.mock.calls -> func函数被调用时接收的参数
// func.mock.result -> func函数被调用时返回值描述

对于 axios 异步请求的测试,mock 发送请求

import axios from 'axios';
jest.mock('axios');

test('测试 axios', async () => {
    axios.get.mockResolvedValue({data: 'hello'})
    await axios.get('/api').then((res) => {
        expect(res.data).toBe('hello')
    })
})

snapshot 测试快照

快照可以防止配置文件误修改;

test('测试配置数据', () => {
    expect(configJson).toMatchSnapshot({
        time: expect.any(Date), // time 是 Date 类型的数据即可
        name: expect.any(String)
    });
})

修改配置文件了之后按 u 更新快照;

对于UI组件的单元测试也比较适用;

mock 进阶 - 异步请求修改为本地 Promise

真正的业务代码:

import axios from 'axios'
export const fetchData = () => {
   return axios.get('/api')
}

方案一 mock 方法

mock: 同级创建 __mocks__ 文件夹,里面创建同名文件

export const fetchData = () => {
   return new Promise((resolve, reject) => {
       resolve({
         status: 200,
         data: 'xxxx'
       })
   })
}

测试代码 (模拟的是 featchData 这个函数):

import { fetchData } from './api.js'
jest.mock('./api.js')

test('fetchData test', () => {
   return fetchData().then(res => {
       expect(res.data).toBe('xxxx')
   })
})

方案二 mock axios

测试代码 (模拟的是 axios 返回值):

import Axios from 'axios'
import { fetchData } from './api.js'

jest.mock('axios')

test('fetchData test', () => {
  Axios.get.mockResolvedValue({
    code: 200,
    data: 'xxxx'
  })
  return fetchData().then(res => {
      expect(res.data).toBe('xxxx')
  })
})

mock timers 定时器测试

import timer from './timer.js'

// 为了解决多个用例对 timer 的影响
beforeEach(() => {
    jest.useFakeTimers() // 使用模拟的 timer,相当于初始化
})

test('测试定时器', () => {
    const fn = jest.fn()
    timer(fn)
    jest.runAllTimers() // 立即执行所有定时器
    expect(fn).toHaveBeenCalledTimes(1) // 定时器被调用了几次
})

// jest.advancedTimersByTime(3000) 快进3秒
// jest.runOnlyPendingTimers() 仅运行当前队列中的 timer

包含 class 类的方法的测试

原理:模拟一个类,去执行模拟的类的方法。

// 如果util是类,会自动重写util中的方法为jest.fn
import demoFun from './demo'
import Util from './util' jest.mock('./util')
// Util => jest.fn()
// Util.a => jest.fn()
// Util.b => jest.fn()

test('测试 class', () => {
    demoFun()
    expect(Util).toHaveBeenCalled()
    expect(Util.mock.instances[0].a).toHaveBeenCalled() // 找实例
    expect(Util.mock.instances[0].b).toHaveBeenCalled()
})