【问题标题】:Testing a material ui slider with @testing-library/react使用 @testing-library/react 测试材质 ui 滑块
【发布时间】: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();
    });
  });
});

【问题讨论】:

    标签: reactjs testing jestjs material-ui react-testing-library


    【解决方案1】:

    经过几个小时的斗争,我终于解决了与测试 MUI 滑块相关的问题

    这真的取决于你需要如何测试你的,在我的情况下,我必须检查标签文本内容是否在使用marks滑块道具点击标记后发生了变化。

    问题

    1) slider 组件根据元素 getBoundingClientRectMouseEvent 计算返回值

    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 &lt;Slider/&gt; 组件的每次使用都将在测试期间呈现为一个简单的 HTML &lt;input/&gt; 元素,使用 Jest 和 react-testing-library 更容易使用。

        {...(others as any)} 是一个 hack,它让我不必担心确保原始组件的每个可能的道具都得到正确处理。根据您所依赖的 Slider 道具,您可能需要在解构过程中拉出额外的道具,以便您可以正确地将它们转换为对香草 &lt;input/&gt; 元素有意义的东西。 See this page in the Material UI docs 了解每个可能的属性。

        【讨论】:

          猜你喜欢
          • 2020-03-15
          • 2019-05-21
          • 2019-11-11
          • 1970-01-01
          • 2023-04-04
          • 1970-01-01
          • 1970-01-01
          • 2019-08-29
          • 2020-06-11
          相关资源
          最近更新 更多