# Jest

# 如何处理对HTTP库的依赖

我们的单元测试显然不能直接调用依赖项HTTP库,需要对HTTP依赖提供mock

正常的调用:

const spyHttp = jest.spyOn(https, 'get').mockImplementationOnce((_, __, cb: any) => {
    const e: any = new EventEmitter()
    e.setEncoding = jest.fn()

    cb(e)

    e.emit('data', '666')
    e.emit('data', '770')
    e.emit('end')

    return e as any
})

downloadImage('https://github.com')

expect(spyHttp).toBeCalledTimes(1)
expect(spyHttp.mock.calls[0][0]).toBe('https://github.com')

处理异常:

test('handle getRawHTML error', async () => {
    jest.spyOn(http, 'get').mockImplementationOnce((_, cb: any) => {
        // 为什么不用上面的EventEmitter 因为有坑
        const res: any = {}
        res.setEncoding = jest.fn()
        res.on = (type: string, onCb: Function) => {
        if (type === 'error')
            onCb(new Error('fake error msg'))
        }
        cb(res)

        return res as any
    })

    try {
        await getRawHTML('fake url')
    }
    catch (e) {
        expect((e as Error).message).toBe('fake error msg')
    }
})

# 如何在某个测试用例中mock依赖

jest.mock方法是作用在整个测试文件层面的,要在某个具体的单元测试mock依赖,可以使用jest.doMock

it('when target is not custom',()=>{

    // 模拟第三方库material-ui
    jest.doMock('@material-ui/core',()=>{
        return {
            __esModule: true,
            FormControl:(props)=>{
                return props.children;
            },
        }
    })

    // 要再次导入才行
    return import('@material-ui/core').then(()=>{
        // 必须要require 动态导入import()有点问题
        const {default:Component} = require('components/BadgeContent')
        const setBadge = jest.fn();
        const {
            container,
        } = render(<Component badge={DEFAULT_BADGE} setBadge={setBadge} />)

        // Act and Assertion
    })
    
})

# 如何测试对某个第三方库的调用

我们的一个组件用到了第三方库:

import copy from "copy-to-clipboard";

当用户点击的时候回调用这个copy方法。

在测试文件中,

// 一定要导入,mock后可以对copy直接断言
import copy from 'copy-to-clipboard'

// 整个文件都mock
jest.mock('copy-to-clipboard',()=>{
    return jest.fn().mockImplementation(()=>{
        return true
    })
})

在具体的test case中:

// 断言调用
expect(copy).toBeCalledTimes(1)

# dynamic require后Invalid hook call

我要测试的组件引入了第三方的UI库,在某个test case中,为了方便测试需要mock某些组件,这个问题上面已经解决了。但是我的组件中使用了react hook,就报错了。jest的issue提到了这个问题,但是个3年都没close掉。

根据其中一个题目中的workaround解决了这个问题。

首先编辑jest.config.js文件,编辑setupFiles属性

const customJestConfig = {
    moduleDirectories: ['node_modules', '<rootDir>/'],
    moduleNameMapper: {
        '@/(.*)$': '<rootDir>/src/$1',
    },
    testEnvironment: 'jest-environment-jsdom',
    // 添加这个jest.setup.js文件,文件名其实可以随意
    setupFiles: ['./jest.setup.js']
}

jest.setup.js文件中写入以下内容:

const RESET_MODULE_EXCEPTIONS = [
    'react',
];

let mockActualRegistry = {};

RESET_MODULE_EXCEPTIONS.forEach(moduleName => {
    jest.doMock(moduleName, () => {
        if (!mockActualRegistry[moduleName]) {
            mockActualRegistry[moduleName] = jest.requireActual(moduleName);
        }
        return mockActualRegistry[moduleName];
    });
});

这样还是有点问题,需要在要测试的case用jest.isolateModules包一层:

it('dynamic require',()=>{
    jest.isolateModules(()=>{
        jest.mock('moduleName',()=>{
            return {}
        })
        // Arrange Act Assert
    })
})

# jsdom

jest基于jsdom模拟DOM,但是这个jsdom其实不支持layout,所以像是 offsetTop innerHeight这种就没法直接处理了。

一个workaround的方法:

Object.defineProperty(window,'innerHeight',{
    writable:true,
    configurable:true,
    value:750
})

Object.defineProperty(HTMLDivElement.prototype,'offsetTop',{
    writable:true,
    configurable:true,
    value: 200,
})

这样强行定义这些和layout有关的属性