React.jsとJest.jsで「TypeError: window.matchMedia is not a function」エラーが発生する原因と解決方法

2024-04-02

Jestテストで発生する「TypeError: window.matchMedia is not a function」エラーの原因と解決方法

React.jsとJest.jsを使ったテストで、TypeError: window.matchMedia is not a function エラーが発生することは、よくある問題です。このエラーは、テスト対象のコードが window.matchMedia APIを使用しているにもかかわらず、Jest環境ではこのAPIが提供されていないために発生します。

原因

Jestはテスト実行時に、実際のブラウザ環境とは異なる仮想環境(jsdom)を使用します。jsdomは軽量なブラウザ環境を提供しますが、すべてのブラウザAPIを実装しているわけではありません。window.matchMedia はブラウザ特有のAPIであり、jsdomでは未実装のため、エラーが発生します。

解決方法

このエラーを解決するには、以下の2つの方法があります。

jest.mock を使って window.matchMedia をモックする

Jestには、モジュールをモックする機能 (jest.mock) があります。この機能を使って、window.matchMedia をモックすることで、テスト環境でもこのAPIを使用できるようにすることができます。

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

上記のコードは、window.matchMedia をモックし、常に matches プロパティが false になるようにしています。テストコードで window.matchMedia を呼び出すと、このモックされたオブジェクトが返されます。

@testing-library/jest-dom ライブラリには、window.matchMedia を含む、いくつかのブラウザAPIをモックする機能が提供されています。このライブラリを使うことで、上記のようなモックコードを記述することなく、window.matchMedia をテスト環境で使用することができます。

import { render, fireEvent } from '@testing-library/react';

const MyComponent = () => {
  const [isMediaMatched, setIsMediaMatched] = useState(false);

  useEffect(() => {
    const mediaQuery = window.matchMedia('(min-width: 768px)');
    mediaQuery.addListener((event) => {
      setIsMediaMatched(event.matches);
    });
  }, []);

  return (
    <div>
      {isMediaMatched ? 'メディアクエリにマッチしています' : 'メディアクエリにマッチしていません'}
    </div>
  );
};

test('メディアクエリにマッチするかどうか', () => {
  const { getByText } = render(<MyComponent />);

  // メディアクエリにマッチするようにイベントを発生させる
  fireEvent(window, new MediaQueryListEvent('change', { matches: true }));

  expect(getByText('メディアクエリにマッチしています')).toBeInTheDocument();
});

上記のコードは、@testing-library/jest-dom ライブラリを使って、window.matchMedia をモックしています。fireEvent 関数を使って、メディアクエリにマッチするイベントを発生させることで、テストコードで isMediaMatched の状態変化を検証することができます。

  • テスト対象のコードが window.matchMedia を直接呼び出す場合は、jest.mock を使ってモックするのが適切です。
  • テスト対象のコードが @testing-library/jest-dom などのライブラリを通じて window.matchMedia を間接的に呼び出す場合は、@testing-library/jest-dom ライブラリを使うのが適切です。

補足

  • window.matchMedia は、メディアクエリに基づいて、画面サイズなどの状態を取得するためのAPIです。
  • Jestは、JavaScriptテストフレームワークです。
  • jsdomは、JavaScriptでブラウザ環境をシミュレートするためのライブラリです。



エラーが発生する例

import React from 'react';

const MyComponent = () => {
  const [isMediaMatched, setIsMediaMatched] = useState(false);

  useEffect(() => {
    const mediaQuery = window.matchMedia('(min-width: 768px)');
    mediaQuery.addListener((event) => {
      setIsMediaMatched(event.matches);
    });
  }, []);

  return (
    <div>
      {isMediaMatched ? 'メディアクエリにマッチしています' : 'メディアクエリにマッチしていません'}
    </div>
  );
};

test('メディアクエリにマッチするかどうか', () => {
  const { getByText } = render(<MyComponent />);

  // エラーが発生する
  expect(getByText('メディアクエリにマッチしています')).toBeInTheDocument();
});

このコードを実行すると、以下のエラーが発生します。

TypeError: window.matchMedia is not a function

jest.mock を使ってエラーを解決する例

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

import React from 'react';

const MyComponent = () => {
  const [isMediaMatched, setIsMediaMatched] = useState(false);

  useEffect(() => {
    const mediaQuery = window.matchMedia('(min-width: 768px)');
    mediaQuery.addListener((event) => {
      setIsMediaMatched(event.matches);
    });
  }, []);

  return (
    <div>
      {isMediaMatched ? 'メディアクエリにマッチしています' : 'メディアクエリにマッチしていません'}
    </div>
  );
};

test('メディアクエリにマッチするかどうか', () => {
  const { getByText } = render(<MyComponent />);

  // エラーが発生せずにテストが実行される
  expect(getByText('メディアクエリにマッチしていません')).toBeInTheDocument();
});

このコードでは、jest.mock を使って window.matchMedia をモックしています。モックオブジェクトは、常に matches プロパティが false になるように設定されています。

import React from 'react';
import { render, fireEvent } from '@testing-library/jest-dom';

const MyComponent = () => {
  const [isMediaMatched, setIsMediaMatched] = useState(false);

  useEffect(() => {
    const mediaQuery = window.matchMedia('(min-width: 768px)');
    mediaQuery.addListener((event) => {
      setIsMediaMatched(event.matches);
    });
  }, []);

  return (
    <div>
      {isMediaMatched ? 'メディアクエリにマッチしています' : 'メディアクエリにマッチしていません'}
    </div>
  );
};

test('メディアクエリにマッチするかどうか', () => {
  const { getByText } = render(<MyComponent />);

  // メディアクエリにマッチするようにイベントを発生させる
  fireEvent(window, new MediaQueryListEvent('change', { matches: true }));

  // エラーが発生せずにテストが実行される
  expect(getByText('メディアクエリにマッチしています')).toBeInTheDocument();
});

その他の解決方法

上記以外にも、以下の解決方法があります。

  • テスト対象のコードを



Jestテストで「TypeError: window.matchMedia is not a function」エラーを解決するその他の方法

jest-environment-jsdom-fourteen ライブラリは、jsdom バージョン 14 を使用するテスト環境を提供します。jsdom バージョン 14 では、window.matchMedia API が実装されています。

// package.json

{
  "dependencies": {
    "jest-environment-jsdom-fourteen": "^1.0.0"
  }
}
// jest.config.js

module.exports = {
  testEnvironment: 'jest-environment-jsdom-fourteen',
};

jsdom-global ライブラリは、jsdom をグローバルオブジェクトに注入することができます。

// package.json

{
  "dependencies": {
    "jsdom-global": "^3.0.2"
  }
}
// テストファイル

import jsdom from 'jsdom-global';

jsdom();

// テストコード

const isMediaMatched = window.matchMedia('(min-width: 768px)').matches;

// ...

if (isMediaMatched) {
  // ...
} else {
  // ...
}

テストコードを修正する

テストコードが window.matchMedia API を直接呼び出す場合は、テストコードを修正して、@testing-library/jest-dom ライブラリの fireEvent 関数を使ってメディアクエリイベントを発生させることができます。

import { fireEvent } from '@testing-library/jest-dom';

// ...

fireEvent(window, new MediaQueryListEvent('change', { matches: true }));

// ...

expect(component).toHaveTextContent('メディアクエリにマッチしています');
  • テスト対象のコードを修正するのが難しい場合は、jest-environment-jsdom-fourteen ライブラリや jsdom-global ライブラリを使うのがおすすめです。
  • テスト対象のコードを修正しても問題ない場合は、@media クエリを使用してコードを修正するのがおすすめです。
  • テストコードを修正しても問題ない場合は、@testing-library/jest-dom ライブラリの fireEvent 関数を使ってテストコードを修正するのがおすすめです。

reactjs jestjs


ReactJSで{this.props.children}にpropsを渡してコンポーネントの使い回りを向上させる

ここでは、{this. props. children}にpropsを渡す3つの方法を解説します。React. cloneElementは、React要素を複製し、新しいpropsを追加する関数です。この関数を使って、{this. props...


初心者でも安心!Webpack Dev Serverを使ってReactJSアプリケーションを開発・公開する

Webpack Dev Serverは、開発中にReactJSアプリケーションを簡単に実行できるツールです。デフォルトではポート8080で実行されますが、ポート80と0. 0.0.0で実行することで、インターネット上の他のユーザーからアクセスできるように公開できます。...


ReactJS で className に動的な値を割り当てる

回答:ReactJS の JSX で className に動的な値を割り当てるには、以下の 2 つの方法があります。方法 1: テンプレートリテラルを使用するテンプレートリテラルは、バッククォート (```) を使用して文字列を定義する方法です。テンプレートリテラル内に式を埋め込むことができ、その式の結果が文字列に展開されます。...


React Input onChange Lag の悩みはこれで解決!原因と対策を徹底解説

原因この現象には、主に以下の原因が考えられます。Controlled Components vs. Uncontrolled Components:Controlled Components:入力フィールドの値を常に state で管理し、onChange イベントで値を更新します。毎回のキーストロークで state を更新し、再描画を発生させるため、処理が重くなる可能性があります。Uncontrolled Components:入力フィールドの値を DOM で管理し、ref を使用して値を取得します。state を更新しないため、再描画が発生せず、パフォーマンスが向上します。...


React StrictMode で発生する findDOMNode の非推奨警告とその解決策

この警告メッセージは、Reactの開発モードである StrictMode で findDOMNode 関数が使用された場合に表示されます。findDOMNode は、Reactコンポーネントインスタンスから対応するDOMノードを取得するために使用される関数です。...