这是我的第一次前端测试体验。在这个项目中,我使用了jest快照测试,并在我的组件中出现了错误TypeError: window.matchMedia is not a function
。
我通过开玩笑的文档,我发现"手动模拟"部分,但我还不知道如何做到这一点。
答案 0 :(得分:20)
我一直在使用这种技术解决一堆嘲弄问题。
describe("Test", () => {
beforeAll(() => {
Object.defineProperty(window, "matchMedia", {
value: jest.fn(() => { return { matches: true } })
});
});
});
或者如果您想一直嘲笑它,您可以将mocks
文件从您的package.json
中调出来:
"setupTestFrameworkScriptFile": "<rootDir>/src/tests/mocks.js",
答案 1 :(得分:14)
最好的文档现在具有“官方”解决方法:
window.matchMedia = jest.fn().mockImplementation(query => {
return {
matches: false,
media: query,
onchange: null,
addListener: jest.fn(),
removeListener: jest.fn(),
};
});
答案 2 :(得分:13)
我在我的jest测试文件中放置了一个matchMedia存根(在测试之上),这允许测试通过:
window.matchMedia = window.matchMedia || function() {
return {
matches : false,
addListener : function() {},
removeListener: function() {}
};
};
答案 3 :(得分:11)
Jest使用jsdom创建浏览器环境。但是,JSDom不支持window.matchMedia
,因此您必须自己创建它。
Jest的manual mocks使用模块边界,即require / import语句,因此它们不适合模拟window.matchMedia
,因为它是全局的。
因此,您有两个选择:
定义您自己的本地matchMedia模块,该模块导出window.matchMedia。 - 这将允许您定义在测试中使用的手动模拟。
定义setup file,将matchMedia的模拟添加到全局窗口。
使用这些选项中的任何一个,您都可以使用matchMedia polyfill作为模拟,这至少可以让您的测试运行,或者如果您需要模拟不同的状态,您可能希望使用私有方法编写自己的状态,配置它的行为类似于Jest fs
manual mock
答案 4 :(得分:7)
刚刚遇到过这个问题。我不得不在jestGlobalMocks.ts中嘲笑这些:
Object.defineProperty(window, 'matchMedia', {
value: () => {
return {
matches: false,
addListener: () => {},
removeListener: () => {}
};
}
});
Object.defineProperty(window, 'getComputedStyle', {
value: () => {
return {
getPropertyValue: () => {}
};
}
});
答案 5 :(得分:3)
您可以使用<div class="linearGradient"></div>
包来测试任何媒体查询(例如设备屏幕更改,颜色方案更改等)
答案 6 :(得分:3)
是创建一个名为 matchMedia.js
的模拟文件并添加以下代码:
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // Deprecated
removeListener: jest.fn(), // Deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
然后,在您的测试文件中,导入您的模拟 import './matchMedia';
只要您在每个用例中导入它,它就应该可以解决您的问题。
我一直遇到这个问题,发现自己做了太多的导入,我想我会提供一个替代解决方案。
即创建一个setup/before.js
文件,内容如下:
import 'regenerator-runtime';
/** Add any global mocks needed for the test suite here */
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // Deprecated
removeListener: jest.fn(), // Deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
然后在 jest.config 文件中添加以下内容:
setupFiles: ['<rootDir>/
路由到您的 BEFORE.JS 文件'],
答案 7 :(得分:2)
官方解决方法对我有用,直到我决定将 react-scripts
从 3.4.1 更新到 4.0.3(因为我使用 create-react-app)。然后我开始收到错误 Cannot read property 'matches' of undefined
。
所以这是我找到的解决方法。安装 mq-polyfill 作为开发依赖项。
然后在src/setupTests.js
中的代码:
import matchMediaPolyfill from 'mq-polyfill'
matchMediaPolyfill(window)
// implementation of window.resizeTo for dispatching event
window.resizeTo = function resizeTo(width, height) {
Object.assign(this, {
innerWidth: width,
innerHeight: height,
outerWidth: width,
outerHeight: height
}).dispatchEvent(new this.Event('resize'))
}
这对我有用。
答案 8 :(得分:0)
尝试以上所有方法均未成功。 将matchMedia.js添加到 mocks 文件夹中,对我来说就做到了。 用@ techguy2000内容填充它:
// __mocks__/matchMedia.js
'use strict';
Object.defineProperty(window, 'matchMedia', {
value: () => ({
matches: false,
addListener: () => {},
removeListener: () => {}
})
});
Object.defineProperty(window, 'getComputedStyle', {
value: () => ({
getPropertyValue: () => {}
})
});
module.exports = window;
然后将其导入setup.js
import matchMedia from '../__mocks__/matchMedia';
轰! :)
答案 9 :(得分:0)
您可以模拟api:
describe("Test", () => {
beforeAll(() => {
Object.defineProperty(window, "matchMedia", {
value: jest.fn(() => {
return {
matches: true,
addListener: jest.fn(),
removeListener: jest.fn()
};
})
});
});
});
答案 10 :(得分:0)
这些家伙通过Jest setupFiles有一个非常漂亮的解决方案:
https://github.com/HospitalRun/components/pull/117/commits/210d1b74e4c8c14e1ffd527042e3378bba064ed8
答案 11 :(得分:0)
TL; DR答案在下方进一步
在我的情况下,答案是不够的,因为window.matchMedia
总是会返回false
(如果更改则返回true
)。我有一些React挂钩和组件,它们需要侦听多个不同的查询,而这些查询可能有不同的matches
。
如果您一次只需要测试一个查询并且您的测试不依赖多个匹配项,那么jest-matchmedia-mock
很有用。但是,据我尝试使用3个小时后的了解,当您调用useMediaQuery
时,以前执行的查询不再起作用。实际上,只要您的代码使用相同的查询调用useMediaQuery
,无论实际的“窗口宽度”如何,您传递给true
的查询都将与window.matchMedia
相匹配。
意识到无法实际使用jest-matchmedia-mock
测试查询后,我对原始答案做了一些更改,以便能够模拟动态查询matches
的行为。此解决方案需要css-mediaquery
npm软件包。
import mediaQuery from "css-mediaquery";
// Mock window.matchMedia's impl.
Object.defineProperty(window, "matchMedia", {
writable: true,
value: jest.fn().mockImplementation((query) => {
const instance = {
matches: mediaQuery.match(query, {
width: window.innerWidth,
height: window.innerHeight,
}),
media: query,
onchange: null,
addListener: jest.fn(), // Deprecated
removeListener: jest.fn(), // Deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
};
// Listen to resize events from window.resizeTo and update the instance's match
window.addEventListener("resize", () => {
const change = mediaQuery.match(query, {
width: window.innerWidth,
height: window.innerHeight,
});
if (change != instance.matches) {
instance.matches = change;
instance.dispatchEvent("change");
}
});
return instance;
}),
});
// Mock window.resizeTo's impl.
Object.defineProperty(window, "resizeTo", {
value: (width: number, height: number) => {
Object.defineProperty(window, "innerWidth", {
configurable: true,
writable: true,
value: width,
});
Object.defineProperty(window, "outerWidth", {
configurable: true,
writable: true,
value: width,
});
Object.defineProperty(window, "innerHeight", {
configurable: true,
writable: true,
value: height,
});
Object.defineProperty(window, "outerHeight", {
configurable: true,
writable: true,
value: height,
});
window.dispatchEvent(new Event("resize"));
},
});
它使用css-mediaquery
和window.innerWidth
来确定查询 ACTUALLY 是否匹配,而不是硬编码的布尔值。它还监听window.resizeTo
模拟实现触发的调整大小事件,以更新matches
值。
您现在可以在测试中使用window.resizeTo
来更改窗口的宽度,以便您对window.matchMedia
的调用反映出该宽度。这是一个仅用于此问题的示例,因此请忽略其存在的性能问题!
const bp = { xs: 200, sm: 620, md: 980, lg: 1280, xl: 1920 };
// Component.tsx
const Component = () => {
const isXs = window.matchMedia(`(min-width: ${bp.xs}px)`).matches;
const isSm = window.matchMedia(`(min-width: ${bp.sm}px)`).matches;
const isMd = window.matchMedia(`(min-width: ${bp.md}px)`).matches;
const isLg = window.matchMedia(`(min-width: ${bp.lg}px)`).matches;
const isXl = window.matchMedia(`(min-width: ${bp.xl}px)`).matches;
console.log("matches", { isXs, isSm, isMd, isLg, isXl });
const width =
(isXl && "1000px") ||
(isLg && "800px") ||
(isMd && "600px") ||
(isSm && "500px") ||
(isXs && "300px") ||
"100px";
return <div style={{ width }} />;
};
// Component.test.tsx
it("should use the md width value", () => {
window.resizeTo(bp.md, 1000);
const wrapper = mount(<Component />);
const div = wrapper.find("div").first();
// console.log: matches { isXs: true, isSm: true, isMd: true, isLg: false, isXl: false }
expect(div.prop("style")).toHaveProperty("width", "600px");
});
注意:安装组件后调整窗口大小时,我尚未测试此行为
答案 12 :(得分:0)
将以下行添加到您的 setupTest.js
文件中,
global.matchMedia = global.matchMedia || function() {
return {
matches : false,
addListener : function() {},
removeListener: function() {}
}
}
这将为您的所有测试用例添加匹配媒体查询。