Jest测试失败:TypeError:window.matchMedia不是函数

时间:2016-10-03 11:31:29

标签: reactjs jestjs

这是我的第一次前端测试体验。在这个项目中,我使用了jest快照测试,并在我的组件中出现了错误TypeError: window.matchMedia is not a function

我通过开玩笑的​​文档,我发现"手动模拟"部分,但我还不知道如何做到这一点。

13 个答案:

答案 0 :(得分:20)

我一直在使用这种技术解决一堆嘲弄问题。

describe("Test", () => {
  beforeAll(() => {  
    Object.defineProperty(window, "matchMedia", {
      value: jest.fn(() => { return { matches: true } })
    });
  });
});

或者如果您想一直嘲笑它,您可以将mocks文件从您的package.json中调出来: "setupTestFrameworkScriptFile": "<rootDir>/src/tests/mocks.js",

参考:setupTestFrameworkScriptFile

答案 1 :(得分:14)

最好的文档现在具有“官方”解决方法:

window.matchMedia = jest.fn().mockImplementation(query => {
  return {
    matches: false,
    media: query,
    onchange: null,
    addListener: jest.fn(),
    removeListener: jest.fn(),
  };
});

Mocking methods which are not implemented in JSDOM

答案 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,因为它是全局的。

因此,您有两个选择:

  1. 定义您自己的本地matchMedia模块,该模块导出window.matchMedia。 - 这将允许您定义在测试中使用的手动模拟。

  2. 定义setup file,将matchMedia的模拟添加到全局窗口。

  3. 使用这些选项中的任何一个,您都可以使用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)

笑话OFFICIAL WORKAROUND

是创建一个名为 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 文件'],

答案 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

Enter image description here

答案 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-mediaquerywindow.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() {}
    }
}

这将为您的所有测试用例添加匹配媒体查询。