sourcecode

Jest 테스트 실패: TypeError: window.matchMedia가 함수가 아닙니다.

codebag 2023. 3. 19. 18:09
반응형

Jest 테스트 실패: TypeError: window.matchMedia가 함수가 아닙니다.

프런트 엔드 테스트는 이번이 처음입니다.했습니다.TypeError: window.matchMedia is not a function내 컴포넌트 안에.

Jest 문서를 살펴보니 "Manual mocks" 섹션을 찾았는데, 아직 어떻게 해야 할지 모르겠어요.

이제 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(),
  })),
});

JSDOM에 구현되지 않은 모킹 메서드

저는 이 기술을 사용하여 많은 조롱의 문제를 해결했습니다.

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",.

레퍼런스: 셋업Test Framework Script 파일

Jest 테스트 파일(테스트 위)에 matchMedia stub을 저장했습니다.이것에 의해, 테스트에 합격할 수 있습니다.

window.matchMedia = window.matchMedia || function() {
    return {
        matches: false,
        addListener: function() {},
        removeListener: function() {}
    };
};

JEEST 공식 회피책

파일,즉 '모크 파일'을 거예요.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';모든 사용 사례에서 Import하기만 하면 문제가 해결될 것입니다.

대체 옵션

저는 이 문제에 계속 부딪혔고, 그저 너무 많은 수입을 하고 있다는 것을 알게 되었고, 다른 해결책을 제시해야겠다고 생각했습니다.

셋업/셋업하다를 만드는 것입니다.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(),
    })),
});

그런 다음 jeast.config 파일에 다음 내용을 추가합니다.

setupFiles: ['<rootDir>/이전으로 가는 길입니다.JS 파일'],

Jest는 jsdom을 사용하여 브라우저 환경을 만듭니다.단, JSDom은 지원하지 않습니다.window.matchMedia직접 작성하셔야 합니다.

Jeest의 수동 모크는 모듈 경계(예: 요구/가져오기 문)와 함께 작동하므로 조롱하기에 적절하지 않습니다.window.matchMedia이치노

따라서 다음 두 가지 옵션이 있습니다.

  1. window.matchMedia를 내보내는 로컬 matchMedia 모듈을 정의합니다.이것에 의해, 테스트에 사용하는 수동 모크를 정의할 수 있습니다.

  2. 글로벌 창에 matchMedia 모크를 추가하는 셋업 파일을 정의합니다.

이러한 옵션 중 하나를 사용하여 match Media polyfill을 모의실험으로 사용할 수 있습니다.최소한 테스트를 실행할 수 있는 경우 또는 다른 상태를 시뮬레이션할 필요가 있는 경우 Jest 수동 모의실험과 유사한 동작을 설정할 수 있는 개인 메서드를 사용하여 match Media polyfill을 직접 작성할 수 있습니다.

API를 조롱할 수 있습니다.

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

저는 방금 이 문제에 직면했고 Global Mocks.ts에서 이것들을 조롱해야 했습니다.

Object.defineProperty(window, 'matchMedia', {
  value: () => {
    return {
      matches: false,
      addListener: () => {},
      removeListener: () => {}
    };
  }
});

Object.defineProperty(window, 'getComputedStyle', {
  value: () => {
    return {
      getPropertyValue: () => {}
    };
  }
});

.jest-matchmedia-mock미디어 쿼리를 테스트하기 위한 패키지(디바이스 화면 변경, 색상 변경 등)

하다, 하다, 하다, 하다, 하다, 하다, 하다, 하다, 하다, 하다, 한 줄씩.setupTest.js 삭제,

global.matchMedia = global.matchMedia || function() {
    return {
        matches : false,
        addListener : function() {},
        removeListener: function() {}
    }
}

그러면 모든 테스트 케이스에 대한 매치 미디어 쿼리가 추가됩니다.

아래 TL;DR 답변

요.window.matchMedia false (오류)true(변경하면)여러 의 서로 다른 쿼리를 수신해야 하는 React 후크 및 컴포넌트가 있었습니다.matches.

내가 시도한 것

개의 .jest-matchmedia-mock도움이 되었습니다.제가 동안 3시간 동안 전화를 때 알 수 것 요.useMediaQuery이전 쿼리는 더 이상 작동하지 않습니다., 즉 전달되는 입니다.useMediaQuery 맞아떨어지다true든지 당신의 window.matchMedia실제 "표준 폭"에 관계없이 동일한 쿼리를 사용합니다.

정답.

질문 내용을 실제로 테스트할 수 없다는 것을 깨달은 후jest-matchmedia-mock 쿼리의 할 수 matches에는 「」가 css-mediaquery packagenpm n n n

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"));
    },
});

it it를 한다.css-mediaquerywindow.innerWidth하드 코딩된 부울 대신 쿼리가 실제로 일치하는지 여부를 확인합니다.또, 에 의해서 발생하는 이벤트의 사이즈 변경도 리슨 합니다.window.resizeTo「」를 disced .matchesdiscloss.discloss 。

이제 사용할 수 있습니다.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");
});

참고: 구성 요소를 장착한 후 윈도우 크기를 조정할 때 이 동작은 테스트하지 않았습니다.

은 제가 업데이트하기로 .react-scripts(create-contract-app) 4.4.1에서 4.0.3으로 설정합니다. 후, 했습니다.Cannot read property 'matches' of undefined.

여기 해결 방법이 있습니다.개발 의존관계로서 mq-polyfill을 인스톨 합니다.

이것을 럼 this로 주세요.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'))
}

이건 나한테 효과가 있었어.

저는 위의 답변들을 모두 시도해 보았지만 아무런 성과가 없었습니다.

mocks 폴더에 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';

쾅! :)

'아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, window.matchMedia하기 전의 입니다.

예:

if (typeof window.matchMedia === 'function') {
    // Do something with window.matchMedia
}

그리고 테스트는 더 이상 실패하지 않을 것이다.

타이프 스크립트를 사용하고 있는 경우는, 아래의 행을 셋업시에 입력합니다.Tests.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(),
    };
  };

이들은 Jest setupFiles를 통해 상당히 슬릭한 솔루션을 제공합니다.

https://github.com/HospitalRun/components/pull/117/commits/210d1b74e4c8c14e1ffd527042e3378bba064ed8

여기에 이미지 설명을 입력하십시오.

왜냐하면 나는 그 도서관을 이용했기 때문이다.window.matchMedia

컴포넌트와 리액트(React)가 합니다.window.matchMedia으로는 jest.isolateModules()

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 https://stackoverflow.com/a/53449595/5923666

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()use Media()를 사용합니다.또한 관련된 어떤 것도 테스트하는 것을 목적으로 하지 않고 윈도 체크를 컴포넌트에 추가하여 메서드를 호출하는 것을 회피할 수 있습니다.

다음 코드 예에서는 Jest에 의해 코드가 실행되면 useMedia 훅은 항상 false를 반환합니다.

모듈 Import를 조롱하는 것에 반대하는 주장에 대한 게시물이 있습니다.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;
}

그러나 메서드에서 반환된 개체에 액세스하려면 파일을 테스트하는 대신 구성 요소 자체에서 개체를 조롱할 수 있습니다.사용 예 참조: (소스 링크)


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모든 테스트에 이 모의실험을 적용하기 위해 jast 설정에 파일을 Import할 수 있습니다(https://www.npmjs.com/package/mock-match-media#jest):

require('mock-match-media/jest-setup);

언급URL : https://stackoverflow.com/questions/39830580/jest-test-fails-typeerror-window-matchmedia-is-not-a-function

반응형