如何让cy.stub()在React组件测试中对导入模块生效?
被测组件代码
// component.tsx import { useNavigate } from 'react-router-dom'; export default function Component() { const navigate = useNavigate(); // ... }
测试代码
// component.cy.tsx import * as reactRouterDom from 'react-router-dom'; cy.stub(reactRouterDom, 'useNavigate') .as('useNavigateStub') .callsFake(() => { console.log(`👾 fake useNavigate`); // <-- 从未执行 }); console.log(`👾 stub: ${reactRouterDom.useNavigate}`); // 打印原useNavigate函数,而非模拟函数 cy.mount(<Component/>);
问题现状
尝试在Cypress组件测试中模拟useNavigate钩子,但模拟完全未生效:fake函数从未执行,打印的useNavigate仍是原函数。推测问题出在Webpack配置上,曾尝试将ts-loader替换为babel-loader,但程序直接挂起;使用ts-loader时,也找不到能让模拟生效的配置。多数时候无错误反馈,调试困难。
当前Cypress Webpack配置
{ component: { devServer: { framework: 'react', bundler: 'webpack', webpackConfig: { entry: './src/index.ts', output: { path: '<repo>/dist/1.35.6', publicPath: 'auto' }, mode: 'development', resolve: { extensions: [ '.ts', '.tsx', '.js', '.jsx' ], alias: { '~': '<repo>/src', test: '<repo>/test' } }, module: { rules: [ { test: /\.(js|jsx|ts|tsx)$/, loader: 'ts-loader', exclude: /node_modules/ }, { test: /\.svg/, type: 'asset/inline' }, { test: /\.png/i, loader: 'file-loader' } ] }, plugins: [ Dotenv { config: { path: './.env', prefix: 'process.env.', systemvars: true } }, ModuleFederationPlugin { // 非常长的配置对象,看似无关紧要 }, HtmlWebpackPlugin { userOptions: { template: './public/index.html' }, version: 5 } ], devtool: 'cheap-module-source-map', devServer: { port: '3003', open: false, historyApiFallback: true } } } } }
已尝试的调整
主要针对ts-loader规则进行修改:
{ test: /\.(js|jsx|ts|tsx)$/, loader: 'ts-loader', exclude: /node_modules/ },
可行解决方向
统一模块实例:Webpack可能对
react-router-dom进行了重复打包,导致测试stub的模块和组件引入的不是同一个实例。在webpackConfig的resolve.alias中添加:alias: { // ...原有别名 'react-router-dom': require.resolve('react-router-dom') }强制所有引入都指向同一个模块文件。
调整ts-loader的模块编译格式:ts-loader默认可能使用ES模块编译,导致Cypress无法拦截stub。修改tsconfig.json的
compilerOptions.module为CommonJS,或者在webpack的ts-loader配置中添加:{ test: /\.(js|jsx|ts|tsx)$/, loader: 'ts-loader', exclude: /node_modules/, options: { compilerOptions: { module: 'CommonJS' } } }简化测试环境的Webpack配置:移除生产环境的非必要插件(如ModuleFederationPlugin、Dotenv),组件测试不需要这些复杂配置,避免插件干扰模块加载逻辑。
改用官方路由模拟方案:放弃手动stub,在mount组件时包裹
<MemoryRouter>,然后通过测试断言导航行为:import { MemoryRouter } from 'react-router-dom'; cy.mount( <MemoryRouter> <Component /> </MemoryRouter> ); // 后续通过触发组件内的导航操作,断言路由变化调试模块加载路径:在测试文件和被测组件中分别添加
console.log(require.resolve('react-router-dom')),对比打印的路径是否一致。如果路径不同,说明模块被多次加载,需要通过resolve配置统一。
内容的提问来源于stack exchange,提问作者Vala

