React组件State Hook测试异常:setCheck未被调用排查
React Hook useState 测试不生效问题排查
组件代码
import React, { SyntheticEvent, useEffect, useState } from 'react'; import { Checkbox, Grid, Header } from 'semantic-ui-react'; interface IIdentityItem { name: string, comment: string, checked: boolean, handleSetCheckState: any, index: number }; export default ({ name, comment, checked, handleSetCheckState, index }: IIdentityItem) => { const [check, setCheck] = useState(true); useEffect(() => { setCheck(checked); }, []); const onChange = (e: SyntheticEvent, data: object) => { setCheck(!check); handleSetCheckState(index, !check); }; return ( <Grid className='p-16 py-9 bg-white'> <Grid.Column width='eleven' textAlign='left'> <Header as='p' className='description'>{name}</Header> <Header as='p' className='comment'>{comment}</Header> </Grid.Column> <Grid.Column width='five' verticalAlign='middle'> <Checkbox toggle checked={check} onChange={onChange} /> </Grid.Column> </Grid> ) }
原测试代码
import ChainItem from './ChainItem'; import React from 'react'; import { create, act } from 'react-test-renderer'; import { BrowserRouter } from 'react-router-dom'; import { Checkbox } from 'semantic-ui-react'; const useStateSpy = jest.spyOn(React, "useState"); describe('ChainItem', () => { let handleSetCheckState, index; beforeEach(() => { handleSetCheckState = jest.fn(); index = 0; //useStateSpy.mockReturnValueOnce([true, setCheck]); }); it('should work', () => { let tree; act(() => { tree = create( <ChainItem handleSetCheckState={handleSetCheckState} index={index} /> ); }); expect(tree).toMatchSnapshot(); }); it('functions ', () => { let setCheck = jest.fn(); useStateSpy.mockImplementationOnce(function() { return [true, setCheck] }); let tree; act(() => { tree = create( <ChainItem handleSetCheckState={handleSetCheckState} checked={true} index={index} /> ); }); const items = tree.root.findAllByType(Checkbox); act(() => items[0].props.onChange({ target: { value: false } })); expect(setCheck).toHaveBeenCalled(); expect(handleSetCheckState).toHaveBeenCalledWith( index, false ); }); afterAll(() => jest.resetModules()); });
问题描述
触发Checkbox的onChange事件后,mock的setCheck并未被调用,React Hook测试不生效,求排查操作错误。
问题原因
- useState Spy 未正确重置:多个测试用例共用同一个全局
useStateSpy,第一个测试用例调用了真实的useState,导致第二个测试用例的mockImplementationOnce无法正确覆盖Hook行为。 - 事件参数传递不匹配:组件的
onChange方法定义了两个参数(SyntheticEvent和data对象),但测试中仅传递了一个参数,可能导致事件处理逻辑异常。 - Spy 生命周期管理不当:未在每个测试用例后恢复
useState的原始实现,导致Spy状态残留影响后续测试。
修复方案
修复后的测试代码
import ChainItem from './ChainItem'; import React from 'react'; import { create, act } from 'react-test-renderer'; import { Checkbox } from 'semantic-ui-react'; describe('ChainItem', () => { let handleSetCheckState, index; let useStateSpy: jest.SpyInstance; beforeEach(() => { handleSetCheckState = jest.fn(); index = 0; // 每个测试用例前重新创建Spy useStateSpy = jest.spyOn(React, "useState"); }); afterEach(() => { // 每个测试用例后恢复useState的原始实现,避免状态残留 useStateSpy.mockRestore(); }); it('should render correctly', () => { let tree; act(() => { tree = create( <ChainItem name="测试名称" comment="测试备注" checked={true} handleSetCheckState={handleSetCheckState} index={index} /> ); }); expect(tree).toMatchSnapshot(); }); it('should trigger setCheck and callback when checkbox toggles', () => { const mockSetCheck = jest.fn(); // 固定mock useState的返回值,确保组件使用我们的mock函数 useStateSpy.mockReturnValue([true, mockSetCheck]); let tree; act(() => { tree = create( <ChainItem name="测试名称" comment="测试备注" checked={true} handleSetCheckState={handleSetCheckState} index={index} /> ); }); // 获取Checkbox组件,传递符合定义的两个参数触发onChange const checkbox = tree.root.findByType(Checkbox); act(() => { checkbox.props.onChange({} as React.SyntheticEvent, {}); }); // 断言setCheck被调用,参数为false(!初始true) expect(mockSetCheck).toHaveBeenCalledWith(false); // 断言父组件回调被正确调用 expect(handleSetCheckState).toHaveBeenCalledWith(index, false); }); });
组件优化建议
- 完善useEffect依赖:当前
useEffect依赖为空数组,当props.checked更新时不会同步check状态,建议补充依赖:
useEffect(() => { setCheck(checked); }, [checked]); // 添加checked作为依赖,实现props更新时同步状态
- 类型安全优化:将
handleSetCheckState的any类型替换为具体类型:
interface IIdentityItem { name: string; comment: string; checked: boolean; handleSetCheckState: (index: number, checked: boolean) => void; // 明确类型 index: number; };
内容的提问来源于stack exchange,提问作者Ivo Sram
相关产品推荐
相关产品推荐

