Trying to test the Material-UI Slider with Reat-Test-Renderer gets an error: Uncaught [TypeError: Cannot read property 'addEventListener' of null]

Codesandbox Link

import React from "react";
import { Slider } from "@material-ui/core";
import renderer from "react-test-renderer";

it("should render", () => {
  renderer.create(<Slider />);
});

This is not the case with any other Material UI components that I know of.

It seems to be related to forwardRef as described here, but I could not figure out a way to get it to work.

EDIT Unfortunately switching over to @testing-library/react is not an option this project I'm working on.

EDIT 2 The reason I am doing this is because I am trying to render and test a more complex component of my own which contains the Slider. It took me a while to figure out that this is what's causing the issue, and the code above is the minimal amount of code to replicate the issue.

EDIT 3 Error message screenshot enter image description here

1

There are 1 answers

3
Zachary Haber On BEST ANSWER

React Test Renderer only renders the components as a JS object rather than to a browser environment. Hence, anything that uses dom based refs will have this issue. Related jest issue: https://github.com/facebook/jest/issues/5462.

There is a section in the React Docs for a way to workaround it: https://reactjs.org/docs/test-renderer.html#ideas

EDIT:

Here's a working test. It likely makes a lot of the functionality of the slider not work, since we are setting the internal refs to null. But it does run. The "Next" branch on MUI doesn't have the findDOMNode function there, so that might be easier to work with.

I wasn't able to get these to work on CodeSandbox because jest was undefined, and I couldn't find how to fix that.

import React from 'react';
import { Slider } from '@material-ui/core';
import renderer from 'react-test-renderer';

jest.mock('react-dom', () => ({
  // the useIsFocusVisible function in Slider.js (MUI component) uses findDOMNOde
  // Luckily it checks if there's nulls, so we can return a null
  // https://github.com/mui-org/material-ui/blob/master/packages/material-ui/src/utils/useIsFocusVisible.js#L136
  findDOMNode: () => null,
}));
it('should render', () => {
  renderer.create(<Slider />);
});

If you want to try and mock the full functionality (sort of), you can use this implementation of the findDOMNode mock, which will return the values that are needed for the useIsFocusVisible to run successfully including adding the event listener:

import React from 'react';
import { Slider } from '@material-ui/core';
import renderer from 'react-test-renderer';

jest.mock('react-dom', () => ({
  // the useIsFocusVisible function in Slider.js (MUI component) uses findDOMNOde
  // https://github.com/mui-org/material-ui/blob/master/packages/material-ui/src/utils/useIsFocusVisible.js#L136
  findDOMNode: (instance) => {
    return { ownerDocument: instance };
  },
}));
it('should render', () => {
  let eventListenerFn = jest.fn();
  renderer.create(<Slider />, {
    createNodeMock: (element) => {
      if (element.type === 'span') {
        return {
          addEventListener: eventListenerFn,
        };
      }
    },
  });
});

In order to figure out how to get these tests to succesfully run, I had to step through the error stacks/MUI source code to find out what's going wrong and what values were needed to be mocked in order to not throw errors.


Original, showing how much better @testing-library is for this:

However, may I suggest using @testing-library? It's nice to work with and uses jsdom to render the components so it'll work with this component.

I went ahead and made a couple of random tests for the Slider using the testing library to show that it works with Slider and throws no errors:

import React from "react";
import { Slider } from "@material-ui/core";
import { render } from "@testing-library/react";

it("should render", () => {
  render(<Slider />);
});

it("should render an input", () => {
  const component = render(<Slider />);
  const input = component.baseElement.querySelector("input");
  expect(input).toBeDefined();
});

it("Should have the correct value", () => {
  const component = render(<Slider value={50} />);
  const input = component.baseElement.querySelector("input");
  expect(input.value).toBe("50");
});

CodeSandbox