You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React函数组件中jest.spyOn单元测试失败问题求助

问题原因与解决方案

为什么会报错?

  1. 函数组件没有instance:你用shallow(<Views {...props} />).instance()获取实例,但函数组件(包括被forwardRef包裹的)不存在类组件那样的实例对象,所以view的值是null,访问view.handleOnConfig自然会抛出Cannot read property 'handleOnConfig' of null错误。
  2. 内部私有函数无法直接监听: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 20:21:10