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

Enzyme测试中模拟MUI useMediaQuery钩子行为遇异常

解决Enzyme测试中MUI useMediaQuery断点助手返回值异常的问题

问题根源分析

你遇到的问题主要来自三个核心点:

  1. shallow渲染的局限性:shallow仅渲染组件本身,不会处理useTheme依赖的ThemeContext,导致theme.breakpoints.down('lg')无法生成正确的媒体查询规则,useMediaQuery自然拿不到正确的匹配结果。
  2. matchMedia配置时机错误:beforeAll仅执行一次,无法在不同测试用例中动态切换屏幕宽度,且初始的window.innerWidth可能不符合你预期的断点条件。
  3. 未强制组件重新渲染:修改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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 16:15:38