vue3 + vite 单元测试编写经验分享(二)
上一篇文章谈了些想法,这篇文章讲介绍工具。
想写测试用例,先来看看测试工具为我们提供了哪些方法。
我们会用到两个工具:
一、vitest
Vitest 是一个由 Vite 提供支持的极速单元测试框架。
官网地址: Vitest
他是个框架,什么是框架,就是提供一个基础测试环境的。
这个环境包含了哪些功能呢?
测试常用的功能:
1、测试分组
一个函数的测试用例,可能需要写很多个,需要做个分组,比如:
import { describe, expect, test } from 'vitest'
const person = {
isActive: true,
age: 32,
describe('person', () => {
test('person is defined', () => {
expect(person).toBeDefined()
test('is active', () => {
expect(person.isActive).toBeTruthy()
test('age limit', () => {
expect(person.age).toBeLessThanOrEqual(32)
2、断言
用来判断结果与预期是否一致 expect(**).toBe(**)
import { expect } from 'vitest'
const input = Math.sqrt(4)
expect(input).toBe(2) // jest AP
除了 toBe 判断值是否相等,还有很多其他的判断方式。
toEqual
断言检查值是否等于接收值,或者是同样的结构,如果是对象类型(将会使用递归的方法进行比较
toStrictEqual
断言检查值是否等于接收值或者同样的结构,如果是对象类型(将会使用递归的方法进行比较),并且会比较它们是否是相同的类型
toContain
用于断言检查值是否在数组中。还可以检查一个字符串是否为另一个字符串的子串。
toHaveLength
用于断言一个对象是否具有
.length
属性,并且它被设置为某个数值。
toHaveProperty
用于断言对象上是否存在指定
key
的属性。
具体的 api 用法请查看官方文档 https:// cn.vitest.dev/api/# expect
3、代理
代理很有用,使用代理你可以屏蔽掉你不想测试的模块,可以营造出一个可靠的环境。
比如页面中有个请求数据的接口,这个接口在单元测试过程中,是不应该触发的。
那就可以使用代理,让这个接口直接返回一个值,不走真实的接口请求。
通过代理返回不同的结果,还可以模拟很多场景,空值的场景,error 的场景。
import { getListData } from '@/api/a'
代理有两个方法
- mock
vi.mock('@/api/a', () => {
return {
getListData: () => Promise.then({data: {a: 1, b: 2}})
这样页面中请求 getListData, 得到的就是 {data: {a: 1, b: 2}} 结果
详情可自行查看文档。
mock 有个致命的缺点,他是一开始最先被初始化的,所以外部声明的变量,他内部是获取不到的。什么意思呢?就是下面的代码,测试用例执行会报错:
a.ts
import { a } from '@/api'
export function fn() {
return a()
a.spec.ts
import { vi, expect, describe, test } from 'vitest'
import { fn } from '../a'
describe('test', () => {
const mockData = []
test('test1', () => {
vi.mock('@/api', () => {
return {
a: () => mockData
expect(fn()).toStrictEqual(mockData)
mockData is not defined
mock 会被提取到最外层,最开始初始化时便执行,这时候还没有执行 const mockData =[] 呢
这里可以将 const mockData =[] 放入 vi.mock 逻辑内部,但外部的 expect 就引用不到 mockData 了。
那怎么办?官方没打算解决。
我觉得有两个办法解决
1、分别声明两个变量,一个给 mock 用,一个给 expect 用。
2、在 mock 中声明变量,绑定到 window(或 global)上,expect 使用时,从 window 上取即可。
- spyOn
在对象的方法或 getter/setter 上创建一个监听
看一下官方的例子:
let apples = 0
const obj = {
getApples: () => 13,
const spy = vi.spyOn(obj, 'getApples').mockImplementation(() => apples)
apples = 1
expect(obj.getApples()).toBe(1)
expect(spy).toHaveBeenCalled()
expect(spy).toHaveReturnedWith(1)
看起来 spyOn 也可以用来做代理,上面的 mockData 案例,用它写一下:
import * as API from '@/api'
const mockData = []
vi.spyOn(API, 'a').mockReturnValue(mockData)
expect(fn()).toStrictEqual(mockData)
看上面的例子,就发现有些时候用 spyOn 比 mock 要方便些
spyOn 也有缺点,他只能代理原对象上已有的属性,此属性不能是 readonly 的,此属性需要是个函数,不能单纯是个值。
spyOn 返回的是 MockInstance, 他有很多个方法,比如上面用的 mockReturnValue 方法,指模拟函数调用时将返回的值。
常用的还有:
mockResolvedValue 当异步函数被调用时,接收一个将被决议 ( resolve ) 的值。
mockRejectedValue 当异步函数被调用时,接收一个将被拒绝 ( reject ) 的错误。
上面两个方法,是代理接口时常用的方法。
4、监听
有时我们需要监听一个函数是否被调用。(很大概率与 代理 搭配着使用)
比如一个页面初始化时,会调用一个接口,我要测试这个功能:初始化时是否调用了接口?
let apples = 0
const obj = {
getApples: () => 13,
const spy = vi.spyOn(obj, 'getApples').mockImplementation(() => apples)
apples = 1