对 Material UI Select 组件的更改做出反应测试库
Posted
技术标签:
【中文标题】对 Material UI Select 组件的更改做出反应测试库【英文标题】:React testing library on change for Material UI Select component 【发布时间】:2019-08-06 14:49:57 【问题描述】:我正在尝试使用react-testing-library 测试Select component 的onChange
事件。
我使用getByTestId
抓取元素,效果很好,然后设置元素的值,然后调用fireEvent.change(select);
,但永远不会调用onChange
,并且永远不会更新状态。
我尝试过使用 select 组件本身以及获取对底层 input
元素的引用,但都不起作用。
有什么解决办法吗?或者这是一个已知问题?
【问题讨论】:
【参考方案1】:material-ui 的 select 组件使用 mouseDown 事件触发弹出菜单出现。如果您使用 fireEvent.mouseDown
应该触发弹出窗口,然后您可以在出现的列表框中单击您的选择。请参阅下面的示例。
import React from "react";
import render, fireEvent, within from "react-testing-library";
import Select from "@material-ui/core/Select";
import MenuItem from "@material-ui/core/MenuItem";
it('selects the correct option', () =>
const getByRole = render(
<>
<Select fullWidth value=selectedTab onChange=onTabChange>
<MenuItem value="privacy">Privacy</MenuItem>
<MenuItem value="my-account">My Account</MenuItem>
</Select>
<Typography variant="h1">/* value set in state */</Typography>
</>
);
fireEvent.mouseDown(getByRole('button'));
const listbox = within(getByRole('listbox'));
fireEvent.click(listbox.getByText(/my account/i));
expect(getByRole('heading').toHaveTextContent(/my account/i);
);
【讨论】:
是的,这是测试它的正确方法。您可以通过检查 material-ui 如何测试其组件github.com/mui-org/material-ui/blob/master/packages/material-ui/… 来获取更多详细信息 如果我有多个 @YaserAliPeedikakkal 如果您的Select
有标签,您可以使用getByLabelText()
定位Select
进行首次点击。带有role="listbox"
的元素会在点击后出现,因此除非您自己添加了带有role="listbox"
的元素,否则下一个查询只会从您的目标点击中找到第一个弹出窗口。例如,user-event
:userEvent.click(getByLabelText("Select Label")); userEvent.click(within(getByRole("listbox")).getByText("Option Text"));
@Kentr 设置标签并单击标签将不起作用,因为标签仅应用于父 div,单击它不会触发弹出窗口打开。
@AswinPrasad 下面是一个使用标签的工作示例:codesandbox。测试没有在代码沙盒上运行,可能是由于我的错误。测试确实在我的计算机上运行(并通过)。【参考方案2】:
当您使用 Material-UI 的 Select
和 native=false
(这是默认设置)时,这会变得非常复杂。这是因为渲染的输入甚至没有<select>
html 元素,而是混合了 div、隐藏输入和一些 svg。然后,当您单击选择时,会显示一个表示层(有点像模式),其中包含您的所有选项(顺便说一下,这些选项不是<option>
HTML 元素),我相信这是单击其中一个这些选项会触发您作为 onChange
回调传递给原始 Material-UI <Select>
的任何内容@
话虽如此,如果您愿意使用<Select native=true>
,那么您将拥有实际的<select>
和<option>
HTML 元素可以使用,并且您可以在<select>
上触发更改事件你会预料到的。
这是来自代码沙箱的测试代码,可以运行:
import React from "react";
import render, cleanup, fireEvent from "react-testing-library";
import Select from "@material-ui/core/Select";
beforeEach(() =>
jest.resetAllMocks();
);
afterEach(() =>
cleanup();
);
it("calls onChange if change event fired", () =>
const mockCallback = jest.fn();
const getByTestId = render(
<div>
<Select
native=true
onChange=mockCallback
data-testid="my-wrapper"
defaultValue="1"
>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
<option value="3">Option 3</option>
</Select>
</div>
);
const wrapperNode = getByTestId("my-wrapper")
console.log(wrapperNode)
// Dig deep to find the actual <select>
const selectNode = wrapperNode.childNodes[0].childNodes[0];
fireEvent.change(selectNode, target: value: "3" );
expect(mockCallback.mock.calls).toHaveLength(1);
);
您会注意到,一旦 Material-UI 渲染出其 <Select>
,您必须深入节点才能找到实际的 <select>
所在的位置。但是一旦你找到它,你可以在上面做一个fireEvent.change
。
CodeSandbox 可以在这里找到:
【讨论】:
感谢@Alvin Lee,这正是我们所需要的。为了将来参考,我们在inputProps
中设置了测试 ID,因此:inputProps= "data-testid": "my-wrapper"
,然后不必通过引用 2 个子节点来获取选择节点。
@RobSanders 很高兴它为你解决了!这是关于设置测试 ID 而不是深入挖掘子节点的有用提示。编码愉快!【参考方案3】:
这是一个带有 Select 选项的 MUI TextField 的工作示例。
沙盒:https://codesandbox.io/s/stupefied-chandrasekhar-vq2x0?file=/src/__tests__/TextSelect.test.tsx:0-1668
文本字段:
import TextField, MenuItem, InputAdornment from "@material-ui/core";
import useState from "react";
export const sampleData = [
name: "Vat-19",
value: 1900
,
name: "Vat-0",
value: 0
,
name: "Vat-7",
value: 700
];
export default function TextSelect()
const [selected, setSelected] = useState(sampleData[0].name);
return (
<TextField
id="vatSelectTextField"
select
label="#ExampleLabel"
value=selected
onChange=(evt) =>
setSelected(evt.target.value);
variant="outlined"
color="secondary"
inputProps=
id: "vatSelectInput"
InputProps=
startAdornment: <InputAdornment position="start">%</InputAdornment>
fullWidth
>
sampleData.map((vatOption) => (
<MenuItem key=vatOption.name value=vatOption.name>
vatOption.name - vatOption.value / 100 %
</MenuItem>
))
</TextField>
);
测试:
import fireEvent, render, screen from "@testing-library/react";
import React from "react";
import act from "react-dom/test-utils";
import TextSelect, sampleData from "../MuiTextSelect/TextSelect";
import "@testing-library/jest-dom";
describe("Tests TextField Select change", () =>
test("Changes the selected value", () =>
const getAllByRole, getByRole, container = render(<TextSelect />);
//CHECK DIV CONTAINER
let vatSelectTextField = container.querySelector(
"#vatSelectTextField"
) as HTMLDivElement;
expect(vatSelectTextField).toBeInTheDocument();
//CHECK DIV CONTAINER
let vatSelectInput = container.querySelector(
"#vatSelectInput"
) as HTMLInputElement;
expect(vatSelectInput).toBeInTheDocument();
expect(vatSelectInput.value).toEqual(sampleData[0].name);
// OPEN
fireEvent.mouseDown(vatSelectTextField);
//CHECKO OPTIONS
expect(getByRole("listbox")).not.toEqual(null);
// screen.debug(getByRole("listbox"));
//CHANGE
act(() =>
const options = getAllByRole("option");
// screen.debug(getAllByRole("option"));
fireEvent.mouseDown(options[1]);
options[1].click();
);
//CHECK CHANGED
vatSelectInput = container.querySelector(
"#vatSelectInput"
) as HTMLInputElement;
expect(vatSelectInput.value).toEqual(sampleData[1].name);
);
);
/**
* HAVE A LOOK AT
*
*
* https://github.com/mui-org/material-ui/blob/master/packages/material-ui/src/Select/Select.test.js
* (ll. 117-121)
*
* https://github.com/mui-org/material-ui/blob/master/packages/material-ui/src/TextField/TextField.test.js
*
*
*/
【讨论】:
【参考方案4】:import * as React from "react";
import ReactDOM from 'react-dom';
import * as TestUtils from 'react-dom/test-utils';
import from "mocha";
import Select from "@material-ui/core/Select";
import MenuItem from "@material-ui/core/MenuItem";
let container;
beforeEach(() =>
container = document.createElement('div');
document.body.appendChild(container);
);
afterEach(() =>
document.body.removeChild(container);
container = null;
);
describe("Testing Select component", () =>
test('start empty, open and select second option', (done) =>
//render the component
ReactDOM.render(<Select
displayEmpty=true
value=""
onChange=(e) =>
console.log(e.target.value);
disableUnderline
classes=
root: `my-select-component`
>
<MenuItem value="">All</MenuItem>
<MenuItem value="1">1</MenuItem>
<MenuItem value="2">2</MenuItem>
<MenuItem value="3">3</MenuItem>
</Select>, container);
//open filter
TestUtils.Simulate.click(container.querySelector('.my-select-component'));
const secondOption = container.ownerDocument.activeElement.parentElement.querySelectorAll('li')[1];
TestUtils.Simulate.click(secondOption);
done();
);
);
【讨论】:
我们可能会走这条路,但我会尽量避免使用 TesUtils.Simulate,因为它不是真实事件,因此也不是我们可以做的最真实的测试。【参考方案5】:it('Set min zoom', async () =>
const minZoomSelect = await waitForElement( () => component.getByTestId('min-zoom') );
fireEvent.click(minZoomSelect.childNodes[0]);
const select14 = await waitForElement( () => component.getByText('14') );
expect(select14).toBeInTheDocument();
fireEvent.click(select14);
);
【讨论】:
【参考方案6】:我在使用 Material UI 选择元素时遇到了一些问题,但最后我找到了这个简单的解决方案。
const handleSubmit = jest.fn()
const renderComponent = (args?: any) =>
const defaultProps =
submitError: '',
allCurrencies: [ name: 'CAD' , name: 'EUR' ],
setSubmitError: () => jest.fn(),
handleSubmit,
handleClose,
const props = ...defaultProps, ...args
return render(<NewAccontForm ...props />)
afterEach(cleanup)
// TEST
describe('New Account Form tests', () =>
it('submits form with corret data', async () =>
const expectedSubmitData =
account_type: 'Personal',
currency_type: 'EUR',
name: 'MyAccount',
const getByRole, getAllByDisplayValue = renderComponent()
const inputs = getAllByDisplayValue('')
fireEvent.change(inputs[0], target: value: 'Personal' )
fireEvent.change(inputs[1], target: value: 'EUR' )
fireEvent.change(inputs[2], target: value: 'MyAccount' )
userEvent.click(getByRole('button', name: 'Confirm' ))
await waitFor(() =>
expect(handleSubmit).toHaveBeenCalledWith(expectedSubmitData)
expect(handleSubmit).toHaveBeenCalledTimes(1)
)
)
)
【讨论】:
【参考方案7】:使用*ByLabelText()
组件
// demo.js
import * as React from "react";
import Box from "@mui/material/Box";
import InputLabel from "@mui/material/InputLabel";
import MenuItem from "@mui/material/MenuItem";
import FormControl from "@mui/material/FormControl";
import Select from "@mui/material/Select";
import Typography from "@mui/material/Typography";
export default function BasicSelect()
const [theThing, setTheThing] = React.useState("None");
const handleChange = (event) =>
setTheThing(event.target.value);
;
return (
<Box sx= minWidth: 120 >
<FormControl fullWidth>
<InputLabel id="demo-simple-select-label">Choose a thing</InputLabel>
<Select
labelId="demo-simple-select-label"
id="demo-simple-select"
value=theThing
label="Choose a thing"
onChange=handleChange
>
<MenuItem value="None">None</MenuItem>
<MenuItem value="Meerkat">Meerkat</MenuItem>
<MenuItem value="Marshmallow">Marshmallow</MenuItem>
</Select>
</FormControl>
<Box sx= padding: 2 >
<Typography>The thing is: theThing</Typography>
</Box>
</Box>
);
测试
// demo.test.js
import "@testing-library/jest-dom";
import render, screen, within from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import Demo from "./demo";
test("When I choose a thing, then the thing changes", async () =>
render(<Demo />);
// Confirm default state.
expect(await screen.findByText(/the thing is: none/i)).toBeInTheDocument();
// Click on the MUI "select" (as found by the label).
const selectLabel = /choose a thing/i;
const selectEl = await screen.findByLabelText(selectLabel);
expect(selectEl).toBeInTheDocument();
userEvent.click(selectEl);
// Locate the corresponding popup (`listbox`) of options.
const optionsPopupEl = await screen.findByRole("listbox",
name: selectLabel
);
// Click an option in the popup.
userEvent.click(within(optionsPopupEl).getByText(/marshmallow/i));
// Confirm the outcome.
expect(
await screen.findByText(/the thing is: marshmallow/i)
).toBeInTheDocument();
);
codesandbox 注意:测试不在codesandbox上运行,但在本地运行并通过。
【讨论】:
以上是关于对 Material UI Select 组件的更改做出反应测试库的主要内容,如果未能解决你的问题,请参考以下文章
在 Material UI Select 组件中搜索输入作为选项
酶不模拟 React Material-UI v1 上的更改事件 - 选择组件
如何使用 Material-Ui Autocomplete for Multi-Select 复选框实现 Formik 的 Field 组件?
Material UI Select Component - 一个组件正在将文本类型的受控输入更改为不受控制