如何用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); }); });
额外说明
原组件存在两个小问题:
initialData初始值为null,直接调用map会抛出错误,需要添加非空判断map生成的<li>元素缺少key属性,这是React的要求
内容的提问来源于stack exchange,提问作者Unmesh
相关产品推荐
相关产品推荐

