一、程序的间接依赖
1. 第三方库/函数
import { fetchData } from './util'
export const sum = (a: number) => a + fetchData(a)
给sum写单侧时,需要使用mock的方式去模拟 fetchData 这个第三方的依赖
import { fetchData } from './util'
import { sum } from './target'
// 使用mock这个api将./util这个文件的导入做了一个模拟,所有引入./util的文件都会被影响到
vi.mock('./util', () => ({
fetchData: vi.fn(),
}));
it('sum', () => {
fetchData.mockReturnValue(2) // 如果是异步的,也有对应的api
expect(sum(1)).toBe(3);
});
2. 第三方的常量
直接赋值即可
单测代码
import INFO from '@/jssdk/common/info';
describe('changeCurHref', () => {
it('changeCurHref', () => {
INFO.isBrower = true
// some code
})
})
3. 类的方法
单测代码
import AppManagerWorker from '@/jssdk/worker/apps';
const appAllHideMock = vi.fn();
AppManagerWorker.prototype.appAllHide = appAllHideMock;
二、全局api
1. window.location
export const changeCurHref = (data: number) => location.href = data > 0 ? '/a' : '/b'
vitest中不易修改location,所以这里做一个简单的模拟
单测代码
import { changeCurHref } from './target'
describe('changeCurHref', () => {
const originLocation = window.location;
const mockReplace = vi.fn()
beforeAll(() => {
const url = originLocation.href;
delete window.location;
window.location = {
href: url,
replace: mockReplace
};
});
afterAll(() => {
window.location = originLocation;
});
it('changeCurHref', () => {
changeCurHref(1)
expect(location.href).toBe('/a');
})
it('replace', () => {
expect(mockReplace).toBeCalledWith('/a');
})
})
2. jsdom中不存在的api
对于一些已经被淘汰了或者jsdom中未实现的api,进行模拟即可
单测代码
import { changeCurHref } from './target'
describe('changeCurHref', () => {
beforeAll(() => {
document.execCommand = vi.fn();
});
})
3. clientWidth
jsdom中很多时候宽度计算有问题,但是需要测试的代码又使用到了该项
单测代码
import { changeCurHref } from './target'
describe('changeCurHref', () => {
let mockClientWidth = 0;
Object.defineProperty(window.HTMLElement.prototype, 'clientWidth', {
get: function() {
return window.mockClientWidth || 0;
}
});
it('clientWidth 为0时', () => {
mockClientWidth = 0;
// some code
});
})
4. 事件监听
业务代码
export function onOrientationChange(options: Partial<Adaptation>) {
window.addEventListener('resize', () => {
setHtmlFontSize(options);
}, false);
}
单测代码
import { onOrientationChange } from './target'
describe('onOrientationChange', () => {
it('绑定对应事件', () => {
vi.spyOn(window, 'addEventListener').mockImplementationOnce(() => {});
onOrientationChange({});
expect(window.addEventListener).toBeCalledWith('resize', expect.any(Function), false);
});
})
5. 随机数/日期
单测代码
const mockTime = 1684307782539;
vi.setSystemTime(mockTime);
vi.spyOn(Math, 'random').mockImplementation(() => 0.123);
三、 异步处理
1. 多个用例使用同一个mocked的函数返回值
使用 concurrent保证用例按序执行
单测代码
import { fetchData } from './util'
import { sum } from './target'
vi.mock('./util', () => ({
fetchData: vi.fn(),
}));
describe.concurrent('sum', () => {
it('fetchData 请求成功时', () => {
fetchData.mockResolvedValue(true);
// some code
});
it('fetchData 请求失败时', () => {
fetchData.mockRejectedValue({ err: 'err msg' });
// some code
});
})