Enzyme测试中模拟MUI useMediaQuery钩子行为遇异常
解决Enzyme测试中MUI useMediaQuery断点助手返回值异常的问题
问题根源分析
你遇到的问题主要来自三个核心点:
shallow渲染的局限性:shallow仅渲染组件本身,不会处理useTheme依赖的ThemeContext,导致theme.breakpoints.down('lg')无法生成正确的媒体查询规则,useMediaQuery自然拿不到正确的匹配结果。matchMedia配置时机错误:beforeAll仅执行一次,无法在不同测试用例中动态切换屏幕宽度,且初始的window.innerWidth可能不符合你预期的断点条件。- 未强制组件重新渲染:修改
window.matchMedia后,组件不会自动重新执行useMediaQuery钩子,需要手动触发重渲染。
修复方案及代码示例
直接替换shallow为mount,并调整测试流程,确保每次测试都能正确设置断点宽度、重置matchMedia并触发组件重渲染:
import React from 'react'; import { mount } from 'enzyme'; import mediaQuery from 'css-mediaquery'; import { ThemeProvider, createTheme } from '@mui/material/styles'; import SessionStore from 'app/stores/SessionStore'; import CustomFields from '../CustomFields'; import CustomFieldButton from '../CustomFieldButton'; import PrepareFieldsList from '../PrepareFieldsList'; // 创建MUI默认主题,确保useTheme能拿到正确的断点配置 const theme = createTheme(); describe('PrepareFieldsList Component', () => { let wrapper; const defaultProps = { customFields: [ { data: null, id: 'fieldId', name: '', required: false, value: 'test', }, ], }; // 生成自定义matchMedia函数,支持传入指定宽度 function createMatchMedia(width) { return (query) => ({ matches: mediaQuery.match(query, { width }), addListener: jest.fn(), removeListener: jest.fn(), // 适配新版matchMedia API addEventListener: jest.fn(), removeEventListener: jest.fn(), dispatchEvent: jest.fn(), }); } beforeEach(() => { // 重置SessionStore mock jest.spyOn(SessionStore, 'getSession').mockReturnValue({ hasFeature: () => true, }); // 初始化默认屏幕宽度(桌面端) window.innerWidth = 1920; window.matchMedia = createMatchMedia(window.innerWidth); // 用mount渲染组件,并包裹ThemeProvider提供上下文 wrapper = mount( <ThemeProvider theme={theme}> <PrepareFieldsList {...defaultProps} /> </ThemeProvider> ); }); // 原有测试用例保持不变 it('should render CustomFieldButton and CustomFields when hasFeature is true', () => { expect(wrapper.find(CustomFieldButton)).toHaveLength(1); expect(wrapper.find(CustomFields)).toHaveLength(1); }); // 测试lg断点以下场景:渲染列表 it('should render the list when screen width is below lg breakpoint', () => { // 设置宽度为lg断点阈值减1(默认lg断点为1200px) const lgBreakpoint = theme.breakpoints.values.lg; window.innerWidth = lgBreakpoint - 1; // 更新matchMedia配置 window.matchMedia = createMatchMedia(window.innerWidth); // 强制组件重新渲染,触发useMediaQuery重新计算 wrapper.setProps({}); expect(wrapper.find('.list')).toHaveLength(1); }); // 测试lg断点及以上场景:不渲染列表 it('should not render the list when screen width is at or above lg breakpoint', () => { const lgBreakpoint = theme.breakpoints.values.lg; window.innerWidth = lgBreakpoint; window.matchMedia = createMatchMedia(window.innerWidth); wrapper.setProps({}); expect(wrapper.find('.list')).toHaveLength(0); }); });
关键修改点说明
- 替换
shallow为mount:确保组件能获取到ThemeContext,正确解析断点规则。 - 包裹
ThemeProvider:如果你的组件在项目中已经被全局ThemeProvider包裹,测试时也需要手动提供,否则useTheme会无法正常工作。 - 动态设置
matchMedia:在每个测试用例中根据需要设置屏幕宽度,并更新window.matchMedia。 - 强制组件重渲染:通过
wrapper.setProps({})触发组件重新执行钩子,获取最新的媒体查询匹配结果。
内容的提问来源于stack exchange,提问作者naemtl
相关产品推荐
相关产品推荐

