MUI SelectをReact Testing Libraryでテストする方法を解説します。
ドロップダウンの開閉、オプションの選択、初期値の確認、複数Selectの操作など、実務で必要なパターンを網羅しています。
動作環境
- @mui/material v6
- @testing-library/react v16
- @testing-library/user-event v14
- vitest v3
基本的なテスト
Selectの取得
MUI Selectはcomboboxロールを持ちます。getByLabelTextまたはgetByRole('combobox')で取得できます。
import FormControl from '@mui/material/FormControl';
import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem';
import Select from '@mui/material/Select';
import {render, screen} from '@testing-library/react';
const BasicSelect = () => {
const [value, setValue] = useState('');
return (
<FormControl fullWidth>
<InputLabel id="category-label">カテゴリー</InputLabel>
<Select
labelId="category-label"
label="カテゴリー"
value={value}
onChange={(e) => setValue(e.target.value)}
>
<MenuItem value="electronics">電子機器</MenuItem>
<MenuItem value="books">書籍</MenuItem>
<MenuItem value="clothing">衣類</MenuItem>
</Select>
</FormControl>
);
};
test('Selectがレンダリングされる', () => {
render(<BasicSelect />);
// getByLabelTextで取得
const select = screen.getByLabelText('カテゴリー');
expect(select).toBeInTheDocument();
// getByRoleでも取得できる
const combobox = screen.getByRole('combobox', {name: 'カテゴリー'});
expect(combobox).toBeInTheDocument();
});ドロップダウンの開閉
Selectをクリックするとドロップダウンが開きます。開いた状態はlistboxロールで確認できます。
import {waitFor} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('Selectをクリックするとドロップダウンが開く', async () => {
const user = userEvent.setup();
render(<BasicSelect />);
const select = screen.getByLabelText('カテゴリー');
await user.click(select);
await waitFor(() => {
expect(screen.getByRole('listbox')).toBeInTheDocument();
});
});
test('ドロップダウンにすべてのオプションが表示される', async () => {
const user = userEvent.setup();
render(<BasicSelect />);
await user.click(screen.getByLabelText('カテゴリー'));
await waitFor(() => {
expect(screen.getByRole('option', {name: '電子機器'})).toBeInTheDocument();
expect(screen.getByRole('option', {name: '書籍'})).toBeInTheDocument();
expect(screen.getByRole('option', {name: '衣類'})).toBeInTheDocument();
});
});オプションの選択
オプションはoptionロールで取得し、クリックで選択します。
test('オプションをクリックして選択できる', async () => {
const user = userEvent.setup();
render(<BasicSelect />);
const select = screen.getByLabelText('カテゴリー');
// ドロップダウンを開く
await user.click(select);
// オプションを選択
await user.click(screen.getByRole('option', {name: '書籍'}));
// 選択値が反映される
expect(screen.getByRole('combobox', {name: 'カテゴリー'})).toHaveTextContent('書籍');
});
test('異なるオプションを選択すると値が更新される', async () => {
const user = userEvent.setup();
render(<BasicSelect />);
const select = screen.getByLabelText('カテゴリー');
// 1回目の選択
await user.click(select);
await user.click(screen.getByRole('option', {name: '書籍'}));
expect(screen.getByRole('combobox', {name: 'カテゴリー'})).toHaveTextContent('書籍');
// 2回目の選択(別のオプション)
await user.click(select);
await user.click(screen.getByRole('option', {name: '電子機器'}));
expect(screen.getByRole('combobox', {name: 'カテゴリー'})).toHaveTextContent('電子機器');
});初期値・選択値の確認
空の初期値(zero-width spaceの罠)
MUI Selectは空の状態でzero-width space(\u200B)を表示します。空かどうかを確認する際は注意が必要です。
test('初期値が空の場合、zero-width spaceが表示される', () => {
render(<BasicSelect />);
const combobox = screen.getByRole('combobox', {name: 'カテゴリー'});
// 空の選択状態ではzero-width spaceのみ
expect(combobox.textContent).toBe('\u200B');
// toHaveTextContent('')は失敗する点に注意
});初期値が設定されている場合
const SelectWithDefault = () => {
const [value, setValue] = useState('books'); // 初期値あり
return (
<FormControl fullWidth>
<InputLabel id="category-label">カテゴリー</InputLabel>
<Select
labelId="category-label"
label="カテゴリー"
value={value}
onChange={(e) => setValue(e.target.value)}
>
<MenuItem value="electronics">電子機器</MenuItem>
<MenuItem value="books">書籍</MenuItem>
<MenuItem value="clothing">衣類</MenuItem>
</Select>
</FormControl>
);
};
test('初期値が設定されている場合、Selectに反映される', () => {
render(<SelectWithDefault />);
const combobox = screen.getByRole('combobox', {name: 'カテゴリー'});
expect(combobox).toHaveTextContent('書籍');
});選択されたオプションのaria-selected確認
test('選択されたオプションにaria-selectedが設定される', async () => {
const user = userEvent.setup();
render(<SelectWithDefault />); // 初期値: books
await user.click(screen.getByLabelText('カテゴリー'));
await waitFor(() => {
const selectedOption = screen.getByRole('option', {name: '書籍'});
expect(selectedOption).toHaveAttribute('aria-selected', 'true');
const unselectedOption = screen.getByRole('option', {name: '電子機器'});
expect(unselectedOption).toHaveAttribute('aria-selected', 'false');
});
});
disabled状態のテスト
const DisabledSelect = ({disabled}: {disabled: boolean}) => {
const [value, setValue] = useState('');
return (
<FormControl fullWidth disabled={disabled}>
<InputLabel id="category-label">カテゴリー</InputLabel>
<Select
labelId="category-label"
label="カテゴリー"
value={value}
onChange={(e) => setValue(e.target.value)}
>
<MenuItem value="books">書籍</MenuItem>
</Select>
</FormControl>
);
};
test('disabled=true でSelectが無効化される', () => {
render(<DisabledSelect disabled={true} />);
const combobox = screen.getByRole('combobox', {name: 'カテゴリー'});
expect(combobox).toHaveAttribute('aria-disabled', 'true');
});
test('disabled=false でSelectが有効化される', () => {
render(<DisabledSelect disabled={false} />);
const combobox = screen.getByRole('combobox', {name: 'カテゴリー'});
expect(combobox).not.toHaveAttribute('aria-disabled', 'true');
});複数Selectの操作
複数のSelectがある場合、それぞれを独立して操作できます。
const MultipleSelectForm = () => {
const [category, setCategory] = useState('');
const [priority, setPriority] = useState('');
return (
<form>
<FormControl fullWidth>
<InputLabel id="category-label">カテゴリー</InputLabel>
<Select
labelId="category-label"
label="カテゴリー"
value={category}
onChange={(e) => setCategory(e.target.value)}
>
<MenuItem value="electronics">電子機器</MenuItem>
<MenuItem value="books">書籍</MenuItem>
</Select>
</FormControl>
<FormControl fullWidth>
<InputLabel id="priority-label">優先度</InputLabel>
<Select
labelId="priority-label"
label="優先度"
value={priority}
onChange={(e) => setPriority(e.target.value)}
>
<MenuItem value="high">高</MenuItem>
<MenuItem value="medium">中</MenuItem>
<MenuItem value="low">低</MenuItem>
</Select>
</FormControl>
</form>
);
};
test('複数のSelectを独立して操作できる', async () => {
const user = userEvent.setup();
render(<MultipleSelectForm />);
const categorySelect = screen.getByLabelText('カテゴリー');
const prioritySelect = screen.getByLabelText('優先度');
// カテゴリーを選択
await user.click(categorySelect);
await user.click(screen.getByRole('option', {name: '書籍'}));
expect(screen.getByRole('combobox', {name: 'カテゴリー'})).toHaveTextContent('書籍');
// 優先度を選択
await user.click(prioritySelect);
await user.click(screen.getByRole('option', {name: '高'}));
expect(screen.getByRole('combobox', {name: '優先度'})).toHaveTextContent('高');
// 両方の値が正しく保持されている
expect(screen.getByRole('combobox', {name: 'カテゴリー'})).toHaveTextContent('書籍');
expect(screen.getByRole('combobox', {name: '優先度'})).toHaveTextContent('高');
});まとめ
この記事では、MUI SelectをReact Testing Libraryでテストする方法を解説しました。
テストパターン一覧
| パターン | 使用するクエリ/マッチャー |
|---|---|
| Selectの取得 | getByLabelText() / getByRole('combobox') |
| ドロップダウン確認 | getByRole('listbox') |
| オプション取得 | getByRole('option', {name: '...'}) |
| 選択値の確認 | toHaveTextContent() |
| 空の確認 | textContent === '\u200B' |
| disabled状態 | toHaveAttribute('aria-disabled', 'true') |
| 選択状態 | toHaveAttribute('aria-selected', 'true') |
ポイント
- MUI Selectは
comboboxロール、開いた状態はlistboxロールで取得 - オプションは
optionロールで取得し、クリックで選択 - 空の初期値はzero-width space(
\u200B)になる点に注意 - disabled状態は
aria-disabled属性で確認 - ドロップダウンの開閉は非同期なので
waitForを使用


コメント