React.jsとJest.jsで「TypeError: window.matchMedia is not a function」エラーが発生する原因と解決方法
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