React函数组件中jest.spyOn单元测试失败问题求助
问题原因与解决方案
为什么会报错?
- 函数组件没有
instance:你用shallow(<Views {...props} />).instance()获取实例,但函数组件(包括被forwardRef包裹的)不存在类组件那样的实例对象,所以view的值是null,访问view.handleOnConfig自然会抛出Cannot read property 'handleOnConfig' of null错误。 - 内部私有函数无法直接监听:
handleOnConfig是组件内部定义的私有函数,不属于组件的公共属性,即使能拿到实例也无法通过jest.spyOn直接监听它。
正确的测试方案
方案一:测试函数的副作用(推荐)
函数组件内部私有函数无法直接访问,应该测试它触发的行为结果(比如setConfig是否被正确调用)。假设你的组件里有useState定义config,示例如下:
组件代码(补全useState)
import { useState, useEffect, forwardRef } from 'react'; import Component from './Component'; const Views = forwardRef(({ user, id }, ref) => { const [config, setConfig] = useState({ theme: 'light' }); const handleOnConfig = (cfg) => { const layout = { ...config }; setConfig(layout); }; useEffect(() => { handleOnConfig(); }, []); return ( <div> <Component user={user} id={id} name='Component' /> </div> ); }); export default Views;
测试代码
import React, { useState } from 'react'; import { shallow } from 'enzyme'; import Views from './Views'; // 模拟useState,捕获setConfig的调用 jest.mock('react', () => ({ ...jest.requireActual('react'), useState: jest.fn(), })); describe('Views', () => { let mockSetConfig; beforeEach(() => { mockSetConfig = jest.fn(); // 给useState预设返回值 useState.mockReturnValue([{ theme: 'light' }, mockSetConfig]); }); test('handleOnConfig triggers setConfig correctly on mount', () => { shallow(<Views user={{}} />); // 验证setConfig被调用1次,且参数是原config的浅拷贝 expect(mockSetConfig).toHaveBeenCalledTimes(1); expect(mockSetConfig).toHaveBeenCalledWith({ theme: 'light' }); }); });
方案二:抽离函数单独测试
如果需要单独测试handleOnConfig的逻辑,可以把它提取到组件外部作为独立函数:
组件代码(抽离函数)
import { useState, useEffect, forwardRef } from 'react'; import Component from './Component'; // 抽离为可导出的独立函数 export const handleOnConfig = (config, setConfig) => { const layout = { ...config }; setConfig(layout); }; const Views = forwardRef(({ user, id }, ref) => { const [config, setConfig] = useState({ theme: 'light' }); useEffect(() => { handleOnConfig(config, setConfig); }, []); return ( <div> <Component user={user} id={id} name='Component' /> </div> ); }); export default Views;
测试代码
import { handleOnConfig } from './Views'; test('handleOnConfig creates a shallow copy of config and calls setConfig', () => { const mockSetConfig = jest.fn(); const testConfig = { theme: 'dark', layout: 'wide' }; handleOnConfig(testConfig, mockSetConfig); expect(mockSetConfig).toHaveBeenCalledTimes(1); // 验证是新对象而非原引用 expect(mockSetConfig.mock.calls[0][0]).toEqual(testConfig); expect(mockSetConfig.mock.calls[0][0]).not.toBe(testConfig); });
内容的提问来源于stack exchange,提问作者ketan
相关产品推荐
相关产品推荐

