使用 @testing-library/react 测试材质 ui 滑块
Posted
技术标签:
【中文标题】使用 @testing-library/react 测试材质 ui 滑块【英文标题】:Testing a material ui slider with @testing-library/react 【发布时间】:2020-03-10 08:54:47 【问题描述】:您好,我正在尝试测试使用 Material-UI 创建的 Slider 组件,但我无法将测试发送到 pass。我想使用fireEvent
和@testing-library/react
来测试值的变化。我一直在关注这篇文章以正确查询 DOM,但我无法获取正确的 DOM 节点。
提前致谢。
<Slider />
组件
// @format
// @flow
import * as React from "react";
import styled from "styled-components";
import Slider as MaterialUISlider from "@material-ui/core";
import withStyles, makeStyles from "@material-ui/core/styles";
import priceRange from "../../../domain/Search/PriceRange/priceRange";
const Wrapper = styled.div`
width: 93%;
display: inline-block;
margin-left: 0.5em;
margin-right: 0.5em;
margin-bottom: 0.5em;
`;
// ommited code pertaining props and styles for simplicity
function Slider(props: SliderProps)
const initialState = [1, 100];
const [value, setValue] = React.useState(initialState);
function onHandleChangeCommitted(e, latestValue)
e.preventDefault();
const onUpdate = props;
const newPriceRange = priceRange(latestValue);
onUpdate(newPriceRange);
function onHandleChange(e, newValue)
e.preventDefault();
setValue(newValue);
return (
<Wrapper
aria-label="range-slider"
>
<SliderWithStyles
aria-labelledby="range-slider"
defaultValue=initialState
// getAriaLabel=index =>
// index === 0 ? "Minimum Price" : "Maximum Price"
//
getAriaValueText=valueText
onChange=onHandleChange
onChangeCommitted=onHandleChangeCommitted
valueLabelDisplay="auto"
value=value
/>
</Wrapper>
);
export default Slider;
Slider.test.js
// @flow
import React from "react";
import cleanup,
render,
getAllByAltText,
fireEvent,
waitForElement from "@testing-library/react";
import "@testing-library/jest-dom/extend-expect";
import Slider from "../Slider";
afterEach(cleanup);
describe("<Slider /> specs", () =>
// [NOTE]: Works, but maybe a better way to do it ?
xdescribe("<Slider /> component aria-label", () =>
it("renders without crashing", () =>
const container = render(<Slider />);
expect(container.firstChild).toBeInTheDocument();
);
);
// [ASK]: How to test the event handlers with fireEvent.
describe("<Slider /> props", () =>
it("display a initial min value of '1'", () =>
const renderResult = render(<Slider />);
// TODO
);
it("display a initial max value of '100'", () =>
const renderResult = render(<Slider />);
// TODO
);
xit("display to values via the onHandleChangeCommitted event when dragging stop", () =>
const renderResult = render(<Slider />);
console.log(renderResult)
// fireEvent.change(renderResult.getByText("1"))
// expect(onChange).toHaveBeenCalled(0);
);
// [NOTE]: Does not work, returns undefined
xit("display to values via the onHandleChange event when dragging stop", () =>
const renderResult = render(<Slider />);
console.log(renderResult.container);
const spanNodeWithAriaAttribute = renderResult.container.firstChild.getElementsByTagName("span")[0].getAttribute('aria-label')
expect(spanNodeWithAriaAttribute).toBe(/range-slider/)
);
);
// [ASK]: Works, but a snapshot is an overkill a better way of doing this ?
xdescribe("<Slider /> snapshot", () =>
it("renders without crashing", () =>
const container = render(<Slider />);
expect(container.firstChild).toMatchSnapshot();
);
);
);
【问题讨论】:
【参考方案1】:经过几个小时的斗争,我终于解决了与测试 MUI 滑块相关的问题
这真的取决于你需要如何测试你的,在我的情况下,我必须检查标签文本内容是否在使用marks
滑块道具点击标记后发生了变化。
问题
1) slider
组件根据元素 getBoundingClientRect
和 MouseEvent
计算返回值
2) 如何查询slider
并触发事件。
3) JSDOM 对读取元素实际高度和宽度的限制导致问题 1
解决方案
1) mock getBoundingClientRect
也应该解决问题 3
2) 将测试 ID 添加到滑块并使用 fireEvent.mouseDown(contaner, ....)
const sliderLabel = screen.getByText("Default text that the user should see")
// add data-testid to slider
const sliderInput = screen.getByTestId("slider")
// mock the getBoundingClientRect
sliderInput.getBoundingClientRect = jest.fn(() =>
return
bottom: 286.22918701171875,
height: 28,
left: 19.572917938232422,
right: 583.0937919616699,
top: 258.22918701171875,
width: 563.5208740234375,
x: 19.572917938232422,
y: 258.22918701171875,
)
expect(sliderInput).toBeInTheDocument()
expect(sliderLabel).toHaveTextContent("Default text that the user should see")
await fireEvent.mouseDown(sliderInput, clientX: 162, clientY: 302 )
expect(sliderLabel).toHaveTextContent(
"New text that the user should see"
)
【讨论】:
【参考方案2】:我建议不要为自定义组件编写测试,并相信该组件适用于我们所有的案例。
阅读this article了解更多详情。因为他们提到了如何为包装 react-select
的组件编写单元测试。
我遵循了类似的方法,为我的第三方滑块组件编写了一个模拟。
在setupTests.js
:
jest.mock('@material-ui/core/Slider', () => (props) =>
const id, name, min, max, onChange, testid = props;
return (
<input
data-testid=testid
type="range"
id=id
name=name
min=min
max=max
onChange=(event) => onChange(event.target.value)
/>
);
);
使用这个模拟,您可以像这样在测试中简单地触发更改事件:
fireEvent.change(getByTestId(`slider`), target: value: 25 );
确保将正确的 testid
作为道具传递给您的 SliderWithStyles
组件
【讨论】:
fireEvent.change(getByTestId('slider'), target: value: 25 );
就是这个变化。感谢这个想法 bdw。比上述解决方案干净得多
虽然这将是一个有效的单元测试,但我认为它不会像 Orville 的解决方案那样增加价值。如果材料 ui 被删除,api 改变,或者这个组件必须重写自定义,所有的测试都必须重写。它的行为与以往一样也不太确定。如果直接测试 range dom 节点,它更接近于用户与组件的交互方式,并且在重写组件时测试不应该改变。【参考方案3】:
我把上面提到的解决方案变成了简单的(Typescript)助手
export class Slider
private static height = 10
// For simplicity pretend that slider's width is 100
private static width = 100
private static getBoundingClientRectMock()
return
bottom: Slider.height,
height: Slider.height,
left: 0,
right: Slider.width,
top: 0,
width: Slider.width,
x: 0,
y: 0,
as DOMRect
static change(element: htmlElement, value: number, min: number = 0, max: number = 100)
const getBoundingClientRect = element.getBoundingClientRect
element.getBoundingClientRect = Slider.getBoundingClientRectMock
fireEvent.mouseDown(
element,
clientX: ((value - min) / (max - min)) * Slider.width,
clientY: Slider.height
)
element.getBoundingClientRect = getBoundingClientRect
用法:
Slider.change(getByTestId('mySlider'), 40) // When min=0, max=100 (default)
// Otherwise
Slider.change(getByTestId('mySlider'), 4, 0, 5) // Sets 4 with scale set to 0-5
【讨论】:
【参考方案4】:在@rehman_00001's answer 的基础上,我为组件创建了一个文件模拟。我是用 TypeScript 编写的,但没有类型它应该也能正常工作。
__mocks__/@material-ui/core/Slider.tsx
import SliderTypeMap from '@material-ui/core';
import React from 'react';
export default function Slider(props: SliderTypeMap['props']): JSX.Element
const onChange, ...others = props;
return (
<input
type="range"
onChange=(event) =>
onChange && onChange(event, parseInt(event.target.value));
...(others as any)
/>
);
现在,Material UI <Slider/>
组件的每次使用都将在测试期间呈现为一个简单的 HTML <input/>
元素,使用 Jest 和 react-testing-library
更容易使用。
...(others as any)
是一个 hack,它让我不必担心确保原始组件的每个可能的道具都得到正确处理。根据您所依赖的 Slider
道具,您可能需要在解构过程中拉出额外的道具,以便您可以正确地将它们转换为对香草 <input/>
元素有意义的东西。 See this page in the Material UI docs 了解每个可能的属性。
【讨论】:
以上是关于使用 @testing-library/react 测试材质 ui 滑块的主要内容,如果未能解决你的问题,请参考以下文章
使用 Jest 和 @testing-library/react-native 测试 React Native 项目时出现“SyntaxError: Cannot use import stateme
如何断言哪个值已传递给 <li> 键属性以与 jest 和 testing-library/react 反应