vitest/jest 踩坑

541 阅读2分钟

一、程序的间接依赖

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
    });
})