React hooks/Jest/Enzyme 测试 useEffect,它在 ref 上添加和删除事件侦听器
React hooks/Jest/Enzyme Test useEffect, which adds and removes event listeners on ref
我有以下组件:
export const DeviceModule = (props: Props) => {
const [isTooltipVisible, changeTooltipVisibility] = useState(false)
const deviceRef = useRef(null)
useEffect(() => {
if (deviceRef && deviceRef.current) {
deviceRef.current.addEventListener(EVENT_TYPE.MOUSEOVER, () => changeTooltipVisibility(true))
deviceRef.current.addEventListener(EVENT_TYPE.MOUSEOUT, () => changeTooltipVisibility(false))
}
return () => {
deviceRef.current.removeEventListener(EVENT_TYPE.MOUSEOVER, () => changeTooltipVisibility(true))
deviceRef.current.removeEventListener(EVENT_TYPE.MOUSEOUT, () => changeTooltipVisibility(false))
}
})
return (
// some jsx. When you hover on a div, it triggers one of the event listeners and changes the state.
)
}
我应该如何使用 Jest 和 Enzyme 对其进行测试?
2021 年更新。请勿使用酶!
原委解释得很透彻here。
简而言之:
- AirBNB(他们创建的)停止支持它,而是把它交给了其他人,现在只有一个人在照顾它。
- 2年没更新了,也就是说不支持react 17(react 18快到了)。 React 17 有 3rd 方适配器,但每个适配器都有其问题,并且面临依赖于项目的相同问题,不能保证被支持。
- 功能成分(如问题中的那个)很难制造,因为酶不是为它们设计的。
- Enzyme 使用了一些内部 React 功能,这是不鼓励的,如果 React 发生变化,可能会产生更糟糕的问题。
- Jest 已经升级了几个版本,使用不同的环境,这让事情变得更加复杂。
现在的行业标准是react-testing-library也是react团队推荐的,他们也停止使用酶了。
您应该使用 jest.mock()
和 jest.requireActual()
来部分模拟 react
模块。就是说你只需要mock useRef
hook,其他保持原样。
使用 ts-jest/utils
的 mocked
辅助函数使您的 TS 类型正确。
使用Object.defineProperty()
定义deviceRef
的setter和getter方法。我们将间谍添加到 setter.
中当前元素的 addEventListener
方法
测试后使用jest.resetAllMocks()
将部分模拟react
模块重置为原始版本。
例如
index.tsx
:
import React, { useState, useRef, useEffect } from 'react';
interface Props {}
export enum EVENT_TYPE {
MOUSEOVER = 'MOUSEOVER',
MOUSEOUT = 'MOUSEOUT',
}
export const DeviceModule = (props: Props) => {
const [isTooltipVisible, changeTooltipVisibility] = useState(false);
const deviceRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (deviceRef && deviceRef.current) {
deviceRef.current.addEventListener(EVENT_TYPE.MOUSEOVER, () => changeTooltipVisibility(true));
deviceRef.current.addEventListener(EVENT_TYPE.MOUSEOUT, () => changeTooltipVisibility(false));
}
return () => {
if (deviceRef && deviceRef.current) {
deviceRef.current.removeEventListener(EVENT_TYPE.MOUSEOVER, () => changeTooltipVisibility(true));
deviceRef.current.removeEventListener(EVENT_TYPE.MOUSEOUT, () => changeTooltipVisibility(false));
}
};
});
return <div ref={deviceRef}>my device module</div>;
};
index.test.tsx
:
import React, { useRef } from 'react';
import { mount } from 'enzyme';
import { DeviceModule, EVENT_TYPE } from './';
import { mocked } from 'ts-jest/utils';
jest.mock('react', () => {
const originReact = jest.requireActual('react');
return {
...originReact,
useRef: jest.fn(),
};
});
const mUseRef = mocked(useRef);
describe('66561050', () => {
afterAll(() => {
jest.resetAllMocks();
});
it('should add event listener for device ref and do cleanup work when component unmount', () => {
const mRef = { current: {} };
let addEventListenerSpy!: jest.SpyInstance;
let removeEventListenerSpy!: jest.SpyInstance;
Object.defineProperty(mRef, 'current', {
get() {
return this._current;
},
set(current) {
if (current) {
addEventListenerSpy = jest.spyOn(current, 'addEventListener');
removeEventListenerSpy = jest.spyOn(current, 'removeEventListener');
}
this._current = current;
},
});
mUseRef.mockReturnValueOnce(mRef);
const wrapper = mount(<DeviceModule />);
expect(addEventListenerSpy).toBeCalledWith(EVENT_TYPE.MOUSEOVER, expect.any(Function));
expect(addEventListenerSpy).toBeCalledWith(EVENT_TYPE.MOUSEOUT, expect.any(Function));
wrapper.unmount();
expect(removeEventListenerSpy).toBeCalledWith(EVENT_TYPE.MOUSEOVER, expect.any(Function));
expect(removeEventListenerSpy).toBeCalledWith(EVENT_TYPE.MOUSEOUT, expect.any(Function));
});
});
单元测试结果:
PASS examples/66561050/index.test.tsx
66561050
✓ should add event listener for device ref and do cleanup work when component unmount (26 ms)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 80.95 | 80 | 50 | 100 |
index.tsx | 80.95 | 80 | 50 | 100 | 14-19
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.744 s
源代码:https://github.com/mrdulin/jest-v26-codelab/tree/main/examples/66561050
2021 年更新。请勿使用酶!
原委解释得很透彻here。
短篇小说:
- AirBNB(他们创建的)停止支持它,而是把它给了别人,现在只有一个人在支持它。
- 已经2年没更新了,也就是说不支持react 17(顺便说一句,react 18快到了)。 React 17 有 3rd 方适配器,但每个适配器都有其问题,并且面临依赖于项目的相同问题,无法保证被支持。
- 功能成分(如问题中的那个)很难制造,因为酶不是为它们设计的。
- Enzyme 使用了一些内部 React 功能,这是不鼓励的,如果 React 发生变化,可能会产生更糟糕的问题。
- Jest 已经升级了几个版本,使用不同的环境,这让事情变得更加复杂。
现在的行业标准是react-testing-library也是react团队推荐的,他们也停止使用酶了。
我有以下组件:
export const DeviceModule = (props: Props) => {
const [isTooltipVisible, changeTooltipVisibility] = useState(false)
const deviceRef = useRef(null)
useEffect(() => {
if (deviceRef && deviceRef.current) {
deviceRef.current.addEventListener(EVENT_TYPE.MOUSEOVER, () => changeTooltipVisibility(true))
deviceRef.current.addEventListener(EVENT_TYPE.MOUSEOUT, () => changeTooltipVisibility(false))
}
return () => {
deviceRef.current.removeEventListener(EVENT_TYPE.MOUSEOVER, () => changeTooltipVisibility(true))
deviceRef.current.removeEventListener(EVENT_TYPE.MOUSEOUT, () => changeTooltipVisibility(false))
}
})
return (
// some jsx. When you hover on a div, it triggers one of the event listeners and changes the state.
)
}
我应该如何使用 Jest 和 Enzyme 对其进行测试?
2021 年更新。请勿使用酶!
原委解释得很透彻here。 简而言之:
- AirBNB(他们创建的)停止支持它,而是把它交给了其他人,现在只有一个人在照顾它。
- 2年没更新了,也就是说不支持react 17(react 18快到了)。 React 17 有 3rd 方适配器,但每个适配器都有其问题,并且面临依赖于项目的相同问题,不能保证被支持。
- 功能成分(如问题中的那个)很难制造,因为酶不是为它们设计的。
- Enzyme 使用了一些内部 React 功能,这是不鼓励的,如果 React 发生变化,可能会产生更糟糕的问题。
- Jest 已经升级了几个版本,使用不同的环境,这让事情变得更加复杂。
现在的行业标准是react-testing-library也是react团队推荐的,他们也停止使用酶了。
您应该使用
jest.mock()
和jest.requireActual()
来部分模拟react
模块。就是说你只需要mockuseRef
hook,其他保持原样。使用
ts-jest/utils
的mocked
辅助函数使您的 TS 类型正确。使用
中当前元素的Object.defineProperty()
定义deviceRef
的setter和getter方法。我们将间谍添加到 setter.addEventListener
方法测试后使用
jest.resetAllMocks()
将部分模拟react
模块重置为原始版本。
例如
index.tsx
:
import React, { useState, useRef, useEffect } from 'react';
interface Props {}
export enum EVENT_TYPE {
MOUSEOVER = 'MOUSEOVER',
MOUSEOUT = 'MOUSEOUT',
}
export const DeviceModule = (props: Props) => {
const [isTooltipVisible, changeTooltipVisibility] = useState(false);
const deviceRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (deviceRef && deviceRef.current) {
deviceRef.current.addEventListener(EVENT_TYPE.MOUSEOVER, () => changeTooltipVisibility(true));
deviceRef.current.addEventListener(EVENT_TYPE.MOUSEOUT, () => changeTooltipVisibility(false));
}
return () => {
if (deviceRef && deviceRef.current) {
deviceRef.current.removeEventListener(EVENT_TYPE.MOUSEOVER, () => changeTooltipVisibility(true));
deviceRef.current.removeEventListener(EVENT_TYPE.MOUSEOUT, () => changeTooltipVisibility(false));
}
};
});
return <div ref={deviceRef}>my device module</div>;
};
index.test.tsx
:
import React, { useRef } from 'react';
import { mount } from 'enzyme';
import { DeviceModule, EVENT_TYPE } from './';
import { mocked } from 'ts-jest/utils';
jest.mock('react', () => {
const originReact = jest.requireActual('react');
return {
...originReact,
useRef: jest.fn(),
};
});
const mUseRef = mocked(useRef);
describe('66561050', () => {
afterAll(() => {
jest.resetAllMocks();
});
it('should add event listener for device ref and do cleanup work when component unmount', () => {
const mRef = { current: {} };
let addEventListenerSpy!: jest.SpyInstance;
let removeEventListenerSpy!: jest.SpyInstance;
Object.defineProperty(mRef, 'current', {
get() {
return this._current;
},
set(current) {
if (current) {
addEventListenerSpy = jest.spyOn(current, 'addEventListener');
removeEventListenerSpy = jest.spyOn(current, 'removeEventListener');
}
this._current = current;
},
});
mUseRef.mockReturnValueOnce(mRef);
const wrapper = mount(<DeviceModule />);
expect(addEventListenerSpy).toBeCalledWith(EVENT_TYPE.MOUSEOVER, expect.any(Function));
expect(addEventListenerSpy).toBeCalledWith(EVENT_TYPE.MOUSEOUT, expect.any(Function));
wrapper.unmount();
expect(removeEventListenerSpy).toBeCalledWith(EVENT_TYPE.MOUSEOVER, expect.any(Function));
expect(removeEventListenerSpy).toBeCalledWith(EVENT_TYPE.MOUSEOUT, expect.any(Function));
});
});
单元测试结果:
PASS examples/66561050/index.test.tsx
66561050
✓ should add event listener for device ref and do cleanup work when component unmount (26 ms)
-----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
-----------|---------|----------|---------|---------|-------------------
All files | 80.95 | 80 | 50 | 100 |
index.tsx | 80.95 | 80 | 50 | 100 | 14-19
-----------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 4.744 s
源代码:https://github.com/mrdulin/jest-v26-codelab/tree/main/examples/66561050
2021 年更新。请勿使用酶!
原委解释得很透彻here。 短篇小说:
- AirBNB(他们创建的)停止支持它,而是把它给了别人,现在只有一个人在支持它。
- 已经2年没更新了,也就是说不支持react 17(顺便说一句,react 18快到了)。 React 17 有 3rd 方适配器,但每个适配器都有其问题,并且面临依赖于项目的相同问题,无法保证被支持。
- 功能成分(如问题中的那个)很难制造,因为酶不是为它们设计的。
- Enzyme 使用了一些内部 React 功能,这是不鼓励的,如果 React 发生变化,可能会产生更糟糕的问题。
- Jest 已经升级了几个版本,使用不同的环境,这让事情变得更加复杂。
现在的行业标准是react-testing-library也是react团队推荐的,他们也停止使用酶了。