Jest test fails : TypeError: window.matchMedia is not a function
Jest test fails : TypeError: window.matchMedia is not a function
这是我的第一次前端测试经历。在这个项目中,我正在使用 Jest 快照测试并在我的组件中出现错误 TypeError: window.matchMedia is not a function
。
我浏览了 Jest 文档,找到了“手动模拟”部分,但我还不知道该怎么做。
Jest 使用 jsdom 创建浏览器环境。但是 JSDom 不支持 window.matchMedia
所以你必须自己创建它。
Jest 的 manual mocks 使用模块边界,即 require / import 语句,因此它们不适合模拟 window.matchMedia
因为它是全局的。
因此您有两个选择:
定义您自己的导出 window.matchMedia 的本地 matchMedia 模块。 -- 这将允许您定义一个手动模拟以在您的测试中使用。
定义一个 setup file,它将 matchMedia 的模拟添加到全局 window。
使用这些选项中的任何一个,您都可以使用 matchMedia polyfill as a mock which would at least allow your tests to run or if you needed to simulate different states you might want to write your own with private methods allowing you to configure it's behaviour similar to the Jest fs
manual mock
我在我的 Jest 测试文件中放置了一个 matchMedia 存根(在测试之上),它允许测试通过:
window.matchMedia = window.matchMedia || function() {
return {
matches: false,
addListener: function() {},
removeListener: function() {}
};
};
我一直在使用这种技术来解决一堆模拟问题。
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",
.
我刚遇到这个问题,不得不在 jestGlobalMocks.ts 中模拟这些:
Object.defineProperty(window, 'matchMedia', {
value: () => {
return {
matches: false,
addListener: () => {},
removeListener: () => {}
};
}
});
Object.defineProperty(window, 'getComputedStyle', {
value: () => {
return {
getPropertyValue: () => {}
};
}
});
我尝试了以上所有答案,但都没有成功。
将 matchMedia.js 添加到 mocks 文件夹,帮我完成了。
我填了 :
// __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 文档现在有一个“官方”解决方法:
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(),
})),
});
你可以模拟 API:
describe("Test", () => {
beforeAll(() => {
Object.defineProperty(window, "matchMedia", {
value: jest.fn(() => {
return {
matches: true,
addListener: jest.fn(),
removeListener: jest.fn()
};
})
});
});
});
这些人通过 Jest setupFiles 有一个非常巧妙的解决方案:
https://github.com/HospitalRun/components/pull/117/commits/210d1b74e4c8c14e1ffd527042e3378bba064ed8
您可以使用 jest-matchmedia-mock
包来测试任何媒体查询(例如设备屏幕更改、配色方案更改等)
TL;DR 在下面进一步回答
在我的例子中,答案是不够的,因为 window.matchMedia
总是 return false
(或者 true
如果你改变它)。我有一些 React 挂钩和组件需要监听 多个不同的 查询,这些查询可能有不同的 matches
.
我试过的
如果您一次只需要测试一个查询并且您的测试不依赖于多个匹配项,jest-matchmedia-mock
很有用。但是,根据我在尝试使用它 3 小时后的理解,当您调用 useMediaQuery
时,您之前所做的查询不再有效。事实上,只要您的代码使用相同的查询调用 window.matchMedia
,您传递给 useMediaQuery
的查询将只匹配 true
,而不管实际的“window 宽度”。
回答
在意识到我无法使用 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 是否匹配而不是 hard-coded 布尔值。它还侦听由 window.resizeTo
模拟实现触发的调整大小事件以更新 matches
值。
您现在可以在测试中使用 window.resizeTo
来更改 window 的宽度,以便您对 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");
});
注意:在安装组件
后调整window大小时,我还没有测试这个行为
开玩笑OFFICIAL WORKAROUND
是创建一个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(),
})),
});
然后,在您的测试文件中,导入您的模拟 import './matchMedia';
只要您在每个用例中都导入它,它应该可以解决您的问题。
ALTERNATIVE OPTION
我一直 运行 解决这个问题,发现自己只是导入了太多东西,我想我会提供一个替代解决方案。
即创建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 文件的路径'],
在我决定将 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'))
}
这对我有用。
将以下行添加到您的 setupTest.js
文件中,
global.matchMedia = global.matchMedia || function() {
return {
matches : false,
addListener : function() {},
removeListener: function() {}
}
}
这将为您的所有测试用例添加匹配媒体查询。
因为我使用了一个使用 window.matchMedia
的库
对我有用的是需要测试中的组件(我使用 React)和 jest.isolateModules()
中的 window.matchMedia
模拟
function getMyComponentUnderTest(): typeof ComponentUnderTest {
let Component: typeof ComponentUnderTest;
// Must use isolateModules because we need to require a new module everytime so
jest.isolateModules(() => {
// Required so the library (inside Component) won't fail as it uses the window.matchMedia
// If we import/require it regularly once a new error will happen:
// `TypeError: Cannot read property 'matches' of undefined`
require('<your-path-to-the-mock>/__mocks__/window/match-media');
Component = require('./<path-to-component>');
});
// @ts-ignore assert the Component (TS screams about using variable before initialization)
// If for some reason in the future the behavior will change and this assertion will fail
// We can do a workaround by returning a Promise and the `resolve` callback will be called with the Component in the `isolateModules` function
// Or we can also put the whole test function inside the `isolateModules` (less preferred)
expect(Component).toBeDefined();
// @ts-ignore the Component must be defined as we assert it
return Component;
}
window.matchMedia
模拟(在 /__mocks__/window/match-media
内):
// Mock to solve: `TypeError: window.matchMedia is not a function`
// From
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: 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(),
});
}),
});
// Making it a module so TypeScript won't scream about:
// TS1208: 'match-media.ts' cannot be compiled under '--isolatedModules' because it is considered a global script file. Add an import, export, or an empty 'export {}' statement to make it a module.
export {};
如果您正在测试的组件包含 window.matchMedia()
或导入另一个组件(即 CSS 媒体查询挂钩使用 useMedia() )并且您不打算测试与之相关的任何内容,您可以通过向组件添加 window 检查来绕过调用该方法。
在下面的示例代码中,如果代码由 Jest 运行,useMedia 钩子将始终 return false。
有一个 post 关于反对模拟模块导入的论点。,https://dev.to/jackmellis/don-t-mock-modules-4jof
import { useLayoutEffect, useState } from 'react';
export function useMedia(query): boolean {
const [state, setState] = useState(false);
useLayoutEffect(() => {
// ******* WINDOW CHECK START *******
if (!window || !window.matchMedia) {
return;
}
// ******* WINDOW CHECK END *******
let mounted = true;
const mql = window.matchMedia(query);
const onChange = () => {
if (!mounted) return;
setState(!!mql.matches);
};
mql.addEventListener('change', onChange);
setState(mql.matches);
return () => {
mounted = false;
mql.removeEventListener('change', onChange);
};
}, [query]);
return state;
}
但是如果您想从方法中访问对象 return,您可以在组件本身中模拟它,而不是测试文件。查看示例用法:(source link)
import {useState, useEffect, useLayoutEffect} from 'react';
import {queryObjectToString, noop} from './utilities';
import {Effect, MediaQueryObject} from './types';
// ************** MOCK START **************
export const mockMediaQueryList: MediaQueryList = {
media: '',
matches: false,
onchange: noop,
addListener: noop,
removeListener: noop,
addEventListener: noop,
removeEventListener: noop,
dispatchEvent: (_: Event) => true,
};
// ************** MOCK END **************
const createUseMedia = (effect: Effect) => (
rawQuery: string | MediaQueryObject,
defaultState = false,
) => {
const [state, setState] = useState(defaultState);
const query = queryObjectToString(rawQuery);
effect(() => {
let mounted = true;
************** WINDOW CHECK START **************
const mediaQueryList: MediaQueryList =
typeof window === 'undefined'
? mockMediaQueryList
: window.matchMedia(query);
************** WINDOW CHECK END **************
const onChange = () => {
if (!mounted) {
return;
}
setState(Boolean(mediaQueryList.matches));
};
mediaQueryList.addListener(onChange);
setState(mediaQueryList.matches);
return () => {
mounted = false;
mediaQueryList.removeListener(onChange);
};
}, [query]);
return state;
};
export const useMedia = createUseMedia(useEffect);
export const useMediaLayout = createUseMedia(useLayoutEffect);
export default useMedia;
我开发了一个专门为此设计的库:https://www.npmjs.com/package/mock-match-media
它提出了 matchMedia
节点的完整实现。
它甚至还有一个 jest-setup
文件,您可以在您的 jest 设置中导入该文件,以将此模拟应用于您的所有测试(参见 https://www.npmjs.com/package/mock-match-media#jest):
require('mock-match-media/jest-setup);
也可以在使用前先测试window.matchMedia
的类型是否为函数
示例:
if (typeof window.matchMedia === 'function') {
// Do something with window.matchMedia
}
而且测试不会再失败
这是我的第一次前端测试经历。在这个项目中,我正在使用 Jest 快照测试并在我的组件中出现错误 TypeError: window.matchMedia is not a function
。
我浏览了 Jest 文档,找到了“手动模拟”部分,但我还不知道该怎么做。
Jest 使用 jsdom 创建浏览器环境。但是 JSDom 不支持 window.matchMedia
所以你必须自己创建它。
Jest 的 manual mocks 使用模块边界,即 require / import 语句,因此它们不适合模拟 window.matchMedia
因为它是全局的。
因此您有两个选择:
定义您自己的导出 window.matchMedia 的本地 matchMedia 模块。 -- 这将允许您定义一个手动模拟以在您的测试中使用。
定义一个 setup file,它将 matchMedia 的模拟添加到全局 window。
使用这些选项中的任何一个,您都可以使用 matchMedia polyfill as a mock which would at least allow your tests to run or if you needed to simulate different states you might want to write your own with private methods allowing you to configure it's behaviour similar to the Jest fs
manual mock
我在我的 Jest 测试文件中放置了一个 matchMedia 存根(在测试之上),它允许测试通过:
window.matchMedia = window.matchMedia || function() {
return {
matches: false,
addListener: function() {},
removeListener: function() {}
};
};
我一直在使用这种技术来解决一堆模拟问题。
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",
.
我刚遇到这个问题,不得不在 jestGlobalMocks.ts 中模拟这些:
Object.defineProperty(window, 'matchMedia', {
value: () => {
return {
matches: false,
addListener: () => {},
removeListener: () => {}
};
}
});
Object.defineProperty(window, 'getComputedStyle', {
value: () => {
return {
getPropertyValue: () => {}
};
}
});
我尝试了以上所有答案,但都没有成功。
将 matchMedia.js 添加到 mocks 文件夹,帮我完成了。
我填了
// __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 文档现在有一个“官方”解决方法:
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(),
})),
});
你可以模拟 API:
describe("Test", () => {
beforeAll(() => {
Object.defineProperty(window, "matchMedia", {
value: jest.fn(() => {
return {
matches: true,
addListener: jest.fn(),
removeListener: jest.fn()
};
})
});
});
});
这些人通过 Jest setupFiles 有一个非常巧妙的解决方案:
https://github.com/HospitalRun/components/pull/117/commits/210d1b74e4c8c14e1ffd527042e3378bba064ed8
您可以使用 jest-matchmedia-mock
包来测试任何媒体查询(例如设备屏幕更改、配色方案更改等)
TL;DR 在下面进一步回答
在我的例子中,答案是不够的,因为 window.matchMedia
总是 return false
(或者 true
如果你改变它)。我有一些 React 挂钩和组件需要监听 多个不同的 查询,这些查询可能有不同的 matches
.
我试过的
如果您一次只需要测试一个查询并且您的测试不依赖于多个匹配项,jest-matchmedia-mock
很有用。但是,根据我在尝试使用它 3 小时后的理解,当您调用 useMediaQuery
时,您之前所做的查询不再有效。事实上,只要您的代码使用相同的查询调用 window.matchMedia
,您传递给 useMediaQuery
的查询将只匹配 true
,而不管实际的“window 宽度”。
回答
在意识到我无法使用 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 是否匹配而不是 hard-coded 布尔值。它还侦听由 window.resizeTo
模拟实现触发的调整大小事件以更新 matches
值。
您现在可以在测试中使用 window.resizeTo
来更改 window 的宽度,以便您对 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");
});
注意:在安装组件
后调整window大小时,我还没有测试这个行为开玩笑OFFICIAL WORKAROUND
是创建一个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(),
})),
});
然后,在您的测试文件中,导入您的模拟 import './matchMedia';
只要您在每个用例中都导入它,它应该可以解决您的问题。
ALTERNATIVE OPTION
我一直 运行 解决这个问题,发现自己只是导入了太多东西,我想我会提供一个替代解决方案。
即创建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 文件的路径'],
在我决定将 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'))
}
这对我有用。
将以下行添加到您的 setupTest.js
文件中,
global.matchMedia = global.matchMedia || function() {
return {
matches : false,
addListener : function() {},
removeListener: function() {}
}
}
这将为您的所有测试用例添加匹配媒体查询。
因为我使用了一个使用 window.matchMedia
对我有用的是需要测试中的组件(我使用 React)和 jest.isolateModules()
window.matchMedia
模拟
function getMyComponentUnderTest(): typeof ComponentUnderTest {
let Component: typeof ComponentUnderTest;
// Must use isolateModules because we need to require a new module everytime so
jest.isolateModules(() => {
// Required so the library (inside Component) won't fail as it uses the window.matchMedia
// If we import/require it regularly once a new error will happen:
// `TypeError: Cannot read property 'matches' of undefined`
require('<your-path-to-the-mock>/__mocks__/window/match-media');
Component = require('./<path-to-component>');
});
// @ts-ignore assert the Component (TS screams about using variable before initialization)
// If for some reason in the future the behavior will change and this assertion will fail
// We can do a workaround by returning a Promise and the `resolve` callback will be called with the Component in the `isolateModules` function
// Or we can also put the whole test function inside the `isolateModules` (less preferred)
expect(Component).toBeDefined();
// @ts-ignore the Component must be defined as we assert it
return Component;
}
window.matchMedia
模拟(在 /__mocks__/window/match-media
内):
// Mock to solve: `TypeError: window.matchMedia is not a function`
// From
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: 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(),
});
}),
});
// Making it a module so TypeScript won't scream about:
// TS1208: 'match-media.ts' cannot be compiled under '--isolatedModules' because it is considered a global script file. Add an import, export, or an empty 'export {}' statement to make it a module.
export {};
如果您正在测试的组件包含 window.matchMedia()
或导入另一个组件(即 CSS 媒体查询挂钩使用 useMedia() )并且您不打算测试与之相关的任何内容,您可以通过向组件添加 window 检查来绕过调用该方法。
在下面的示例代码中,如果代码由 Jest 运行,useMedia 钩子将始终 return false。
有一个 post 关于反对模拟模块导入的论点。,https://dev.to/jackmellis/don-t-mock-modules-4jof
import { useLayoutEffect, useState } from 'react';
export function useMedia(query): boolean {
const [state, setState] = useState(false);
useLayoutEffect(() => {
// ******* WINDOW CHECK START *******
if (!window || !window.matchMedia) {
return;
}
// ******* WINDOW CHECK END *******
let mounted = true;
const mql = window.matchMedia(query);
const onChange = () => {
if (!mounted) return;
setState(!!mql.matches);
};
mql.addEventListener('change', onChange);
setState(mql.matches);
return () => {
mounted = false;
mql.removeEventListener('change', onChange);
};
}, [query]);
return state;
}
但是如果您想从方法中访问对象 return,您可以在组件本身中模拟它,而不是测试文件。查看示例用法:(source link)
import {useState, useEffect, useLayoutEffect} from 'react';
import {queryObjectToString, noop} from './utilities';
import {Effect, MediaQueryObject} from './types';
// ************** MOCK START **************
export const mockMediaQueryList: MediaQueryList = {
media: '',
matches: false,
onchange: noop,
addListener: noop,
removeListener: noop,
addEventListener: noop,
removeEventListener: noop,
dispatchEvent: (_: Event) => true,
};
// ************** MOCK END **************
const createUseMedia = (effect: Effect) => (
rawQuery: string | MediaQueryObject,
defaultState = false,
) => {
const [state, setState] = useState(defaultState);
const query = queryObjectToString(rawQuery);
effect(() => {
let mounted = true;
************** WINDOW CHECK START **************
const mediaQueryList: MediaQueryList =
typeof window === 'undefined'
? mockMediaQueryList
: window.matchMedia(query);
************** WINDOW CHECK END **************
const onChange = () => {
if (!mounted) {
return;
}
setState(Boolean(mediaQueryList.matches));
};
mediaQueryList.addListener(onChange);
setState(mediaQueryList.matches);
return () => {
mounted = false;
mediaQueryList.removeListener(onChange);
};
}, [query]);
return state;
};
export const useMedia = createUseMedia(useEffect);
export const useMediaLayout = createUseMedia(useLayoutEffect);
export default useMedia;
我开发了一个专门为此设计的库:https://www.npmjs.com/package/mock-match-media
它提出了 matchMedia
节点的完整实现。
它甚至还有一个 jest-setup
文件,您可以在您的 jest 设置中导入该文件,以将此模拟应用于您的所有测试(参见 https://www.npmjs.com/package/mock-match-media#jest):
require('mock-match-media/jest-setup);
也可以在使用前先测试window.matchMedia
的类型是否为函数
示例:
if (typeof window.matchMedia === 'function') {
// Do something with window.matchMedia
}
而且测试不会再失败