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
相关产品推荐
相关产品推荐

