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

如何用Mocha、Enzyme和Chai测试React中useEffect仅执行一次

解决React函数组件useEffect仅执行一次的测试问题

错误原因

你遇到的TypeError: Attempted to wrap undefined property useEffect as function是因为函数组件没有prototype属性。类组件的生命周期方法挂载在prototype上,但函数组件的hooks是React内部调用的,无法通过xyz.prototype.useEffect访问到,自然无法用Sinon进行spy操作。

解决方案

方法1:Spy React.useEffect全局方法

直接监听React的useEffect方法,验证其调用次数,注意测试后要恢复原方法避免影响其他测试:

import React from 'react';
import { mount } from 'enzyme';
import { expect } from 'chai';
import sinon from 'sinon';
import xyz from './../components/xyz';

describe.only('<xyz/>', function() {
  let useEffectSpy;

  beforeEach(() => {
    // 监听React.useEffect
    useEffectSpy = sinon.spy(React, 'useEffect');
  });

  afterEach(() => {
    // 恢复原方法,避免污染其他测试
    useEffectSpy.restore();
  });

  it('useEffect 仅执行一次', () => {
    mount(<xyz />);
    expect(useEffectSpy.calledOnce).to.equal(true);
  });
});

方法2:间接验证(监听内部调用的函数)

因为useEffect的空依赖数组保证它只执行一次,所以可以监听它内部调用的getInitialData函数,通过验证该函数的调用次数间接确认useEffect的执行次数。需要先调整组件结构,把getInitialData暴露出来:

修改后的组件代码

import React, { useState, useEffect } from 'react';

// 将getInitialData提取为可导出的函数,方便测试
export async function getInitialData(api) {
  const response = await api(); // 补充await,原代码缺少异步处理
  return response.data;
}

const xyz = () => {
  const [initialData, setInitialData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      const data = await getInitialData(api); // 替换为你的实际请求函数
      setInitialData(data);
    };
    fetchData();
  }, []);

  // 修复initialData为null时map报错的问题,添加key属性
  return (
    <div>
      {initialData ? initialData.map((item, index) => <li key={index}></li>) : null}
    </div>
  );
};

export default xyz;

测试代码

import React from 'react';
import { mount } from 'enzyme';
import { expect } from 'chai';
import sinon from 'sinon';
import xyz, { getInitialData } from './../components/xyz';
// 导入你的api模块,比如import * as apiModule from './api';

describe.only('<xyz/>', function() {
  let getInitialDataSpy;
  // let apiStub;

  beforeEach(() => {
    // 监听getInitialData
    getInitialDataSpy = sinon.spy(getInitialData);
    // Mock api请求,避免实际调用接口
    // apiStub = sinon.stub(apiModule, 'api').resolves({ data: [] });
  });

  afterEach(() => {
    getInitialDataSpy.restore();
    // apiStub.restore();
  });

  it('getInitialData 仅被调用一次(间接验证useEffect执行一次)', () => {
    mount(<xyz />);
    expect(getInitialDataSpy.calledOnce).to.equal(true);
  });
});

额外说明

原组件存在两个小问题:

  1. initialData初始值为null,直接调用map会抛出错误,需要添加非空判断
  2. map生成的<li>元素缺少key属性,这是React的要求

内容的提问来源于stack exchange,提问作者Unmesh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 12:01:20