这是我的第一个前端测试经验。在这个项目中,我正在使用Jest快照测试,并且TypeError: window.matchMedia is not a function
组件内部出现错误。
我浏览了Jest文档,找到了“手动模拟”部分,但是我还不知道如何做。
Answers:
Jest文档现在有一个“官方”解决方法:
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(),
})),
});
我一直在使用这种技术来解决许多模拟问题。
describe("Test", () => {
beforeAll(() => {
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(),
}))
});
});
});
或者,如果您想一直模拟它,则可以将您的mocks
文件从中调用package.json
:
"setupFilesAfterEnv": "<rootDir>/src/tests/mocks.js",
。
TypeError: Cannot read property 'matches' of undefined
例外
setupFilesAfterEnv
不setupFiles
呢?
我在我的Jest测试文件中放置了matchMedia存根(在测试之上),该文件允许测试通过:
window.matchMedia = window.matchMedia || function() {
return {
matches: false,
addListener: function() {},
removeListener: function() {}
};
};
global.window.matchMedia = jest.fn(() => { return { matches: false, addListener: jest.fn(), removeListener: jest.fn() } })
Jest使用jsdom创建浏览器环境。但是,JSm不支持,window.matchMedia
因此您必须自己创建它。
Jest的手动模拟具有模块边界,即require / import语句,因此它们不适合按window.matchMedia
原样进行模拟,因为它是全局的。
因此,您有两种选择:
定义自己的本地matchMedia模块,该模块导出window.matchMedia。-这样您就可以定义一个要在测试中使用的手动模拟。
使用这些选项中的任一个,您都可以将matchMedia polyfill用作模拟,这至少将允许您运行测试,或者如果您需要模拟不同的状态,则可能需要使用私有方法编写自己的状态,从而允许您配置其行为类似于开玩笑的fs
手动模拟
我刚遇到此问题,不得不在jestGlobalMocks.ts中模拟它们:
Object.defineProperty(window, 'matchMedia', {
value: () => {
return {
matches: false,
addListener: () => {},
removeListener: () => {}
};
}
});
Object.defineProperty(window, 'getComputedStyle', {
value: () => {
return {
getPropertyValue: () => {}
};
}
});
我尝试了上述所有以上答案,但均未成功。
添加matchMedia.js的嘲笑文件夹,这样做是对我来说。
我用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';
繁荣!:)
这些家伙通过Jest setupFiles有一个非常漂亮的解决方案:
https://github.com/HospitalRun/components/pull/117/commits/210d1b74e4c8c14e1ffd527042e3378bba064ed8
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");
});
注意:安装组件后调整窗口大小时,我尚未测试此行为