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

Vitest+Testing Library测试CSS @media元素可见性失败排查

问题描述

我用npm create vite@latest搭建了Vite + Vitest的标准React+TypeScript项目,参照Vitest GitHub示例配置了Vitest。现在要测试组件中受CSS @media规则控制的文本标签显示/隐藏逻辑,写了测试用例但移动端测试失败:视口宽度设为414px时,预期隐藏的desktop-only元素没被识别为不可见,桌面端测试正常,而且组件内window.innerWidth返回值是对的。试了其他设置视口的方法,结果一样。测试用例代码如下:

import { describe, test, expect, vi, afterEach } from "vitest";
import { render, screen } from "@testing-library/react";
import App from "./App";

afterEach(() => {
  vi.unstubAllGlobals();
});

describe("App", () => {
  test("Test on mobile", () => {
    vi.stubGlobal("innerWidth", 414);
    render(<App />);
    expect(window.innerWidth).toEqual(414);
    expect(screen.getByTestId("always")).toBeVisible();
    expect(screen.getByTestId("desktop-only")).not.toBeVisible();
  });
  test("Test on desktop", () => {
    vi.stubGlobal("innerWidth", 1200);
    render(<App />);
    expect(window.innerWidth).toEqual(1200);
    expect(screen.getByTestId("always")).toBeVisible();
    expect(screen.getByTestId("desktop-only")).toBeVisible();
  });
});
问题原因及解决方法

核心原因

你用vi.stubGlobal("innerWidth")只是篡改了window.innerWidth的返回值,但JSDOM(Vitest默认测试环境)并不会根据这个值模拟CSS @media规则的生效逻辑。CSS媒体查询的判断依赖JSDOM内部的视口配置,和单纯的window.innerWidth值无关。

解决步骤

方法1:直接修改JSDOM视口配置

在测试中调整JSDOM的视口尺寸,触发媒体查询生效:

test("Test on mobile", () => {
  // 设置JSDOM视口宽高
  document.documentElement.style.width = "414px";
  document.documentElement.style.height = "896px";
  // 触发resize事件确保样式更新
  document.documentElement.dispatchEvent(new Event("resize"));

  render(<App />);
  
  expect(screen.getByTestId("always")).toBeVisible();
  expect(screen.getByTestId("desktop-only")).not.toBeVisible();
});

方法2:用专门工具模拟媒体查询

安装jest-matchmedia-mock来精准模拟媒体查询:

npm install jest-matchmedia-mock --save-dev

测试文件中使用:

import matchMediaMock from "jest-matchmedia-mock";

let mediaMock: matchMediaMock;

beforeEach(() => {
  mediaMock = new matchMediaMock();
});

afterEach(() => {
  mediaMock.clear();
});

test("Test on mobile", () => {
  // 模拟移动端媒体查询条件
  mediaMock.useMediaQuery("(max-width: 768px)");
  
  render(<App />);
  
  expect(screen.getByTestId("always")).toBeVisible();
  expect(screen.getByTestId("desktop-only")).not.toBeVisible();
});

方法3:改用JS控制元素显隐

把CSS媒体查询逻辑换成JS判断,这样篡改window.innerWidth就能直接生效:

// App.tsx
import { useState, useEffect } from "react";

function App() {
  const [isDesktop, setIsDesktop] = useState(window.innerWidth >= 768);

  useEffect(() => {
    const handleResize = () => {
      setIsDesktop(window.innerWidth >= 768);
    };
    window.addEventListener("resize", handleResize);
    return () => window.removeEventListener("resize", handleResize);
  }, []);

  return (
    <div>
      <div data-testid="always">Always visible</div>
      {isDesktop && <div data-testid="desktop-only">Desktop only</div>}
    </div>
  );
}

export default App;

这种方式下你原来的测试用例无需修改就能直接通过。

内容的提问来源于stack exchange,提问作者zakharov.arthur

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 01:57:22