这是我的第一次前端测试体验.在这个项目中,我正在使用jest快照测试并TypeError: window.matchMedia is not a function在我的组件中出错.
我通过jest文档,我找到了"手动模拟"部分,但我还不知道如何做到这一点.
Sel*_*ond 34
jest docs现在有一个"官方"解决方法:
window.matchMedia = jest.fn().mockImplementation(query => {
return {
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
};
});
Run Code Online (Sandbox Code Playgroud)
Gab*_*MSC 28
就是创建一个mock文件,调用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(),
})),
});
Run Code Online (Sandbox Code Playgroud)
然后,在您的测试文件中导入您的模拟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(),
})),
});
Run Code Online (Sandbox Code Playgroud)
然后在 jest.config 文件中添加以下内容:
setupFiles: ['<rootDir>/路由到您的 before.js 文件'],
Max*_*amp 23
我一直在使用这种技术来解决一堆嘲弄问题.
describe("Test", () => {
beforeAll(() => {
Object.defineProperty(window, "matchMedia", {
value: jest.fn(() => { return { matches: true } })
});
});
});
Run Code Online (Sandbox Code Playgroud)
或者如果你想一直嘲笑它,你可以放入你的mocks文件中调用package.json:
"setupTestFrameworkScriptFile": "<rootDir>/src/tests/mocks.js",
ref:setupTestFrameworkScriptFile
小智 16
将以下行添加到您的setupTest.js文件中,
global.matchMedia = global.matchMedia || function() {
return {
matches : false,
addListener : function() {},
removeListener: function() {}
}
}
Run Code Online (Sandbox Code Playgroud)
这将为您的所有测试用例添加匹配媒体查询。
Ben*_*uff 13
我在我的jest测试文件中(在测试之上)放了一个matchMedia存根,允许测试通过:
window.matchMedia = window.matchMedia || function() {
return {
matches : false,
addListener : function() {},
removeListener: function() {}
};
};
Run Code Online (Sandbox Code Playgroud)
ris*_*ott 12
Jest使用jsdom创建浏览器环境.但JSDom不支持,window.matchMedia因此您必须自己创建它.
Jest的手动模拟使用模块边界,即需要/导入语句,因此它们不适合模拟window.matchMedia,因为它是全局的.
因此,您有两种选择:
定义您自己的本地matchMedia模块,它导出window.matchMedia. - 这将允许您定义在测试中使用的手动模拟.
定义一个设置文件,将matchMedia的模拟添加到全局窗口.
使用这些选项中的任何一个,您可以使用matchMedia polyfill作为模拟,这至少允许您的测试运行,或者如果您需要模拟不同的状态,您可能希望使用私有方法编写自己的状态,允许您配置它的行为类似于Jest fs手动模拟
刚刚遇到这个问题.我不得不在jestGlobalMocks.ts中嘲笑这些:
Object.defineProperty(window, 'matchMedia', {
value: () => {
return {
matches: false,
addListener: () => {},
removeListener: () => {}
};
}
});
Object.defineProperty(window, 'getComputedStyle', {
value: () => {
return {
getPropertyValue: () => {}
};
}
});
Run Code Online (Sandbox Code Playgroud)
您可以模拟 API:
describe("Test", () => {
beforeAll(() => {
Object.defineProperty(window, "matchMedia", {
value: jest.fn(() => {
return {
matches: true,
addListener: jest.fn(),
removeListener: jest.fn()
};
})
});
});
});
Run Code Online (Sandbox Code Playgroud)
如果您使用的是打字稿,请将以下行放入 setupTests.ts 文件中。这对我有用:
export default global.matchMedia =
global.matchMedia ||
function (query) {
return {
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
};
};
Run Code Online (Sandbox Code Playgroud)
TL;DR 答案在下面
就我而言,答案是不够的,因为window.matchMedia总是会返回false(或者true如果您更改它)。我有一些 React 挂钩和组件,需要用可能不同的matches.
如果您一次只需要测试一个查询并且您的测试不依赖于多个匹配,那么这jest-matchmedia-mock是有用的。然而,根据我在尝试使用它 3 小时后了解到的情况,当您致电 时useMediaQuery,您之前所做的查询将不再起作用。事实上,只要您的代码使用同一查询调用,您传入的查询useMediaQuery就会匹配,而不管实际的“窗口宽度”如何。truewindow.matchMedia
在意识到我无法实际测试我的查询后jest-matchmedia-mock,我对原始答案进行了一些更改,以便能够模拟动态查询的行为matches。此解决方案需要css-mediaquerynpm 包。
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"));
},
});
Run Code Online (Sandbox Code Playgroud)
它使用css-mediaquery来window.innerWidth确定查询是否实际匹配,而不是使用硬编码的布尔值。它还侦听模拟实现触发的调整大小事件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");
});
Run Code Online (Sandbox Code Playgroud)
注意:在安装组件后调整窗口大小时,我尚未测试此行为
官方的解决方法对我有用,直到我决定从 3.4.1 更新react-scripts到 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'))
}
Run Code Online (Sandbox Code Playgroud)
这对我有用。