2

Hi I have a functional component as shown below:

import React, { useRef, useEffect, useState } from 'react';

const SomeComponent = ({ prop1, ...otherProps}) => {
  const divRef = useRef();

  useEffect(() => {
    divRef.current.addEventListener('mousedown', mouseDownFunc);
  }, []);

  const mouseDownFunc = () => {
    document.addEventListener('mousemove', (el) => {
        // call some parent function
    });
  }

  return (
    <div
        className='test-div'
        ref={ divRef }>
    </div>
  );
};

How do I test a react functional component wherein addEventListener is added using ref inside useEffect which when triggered calls mouseDownFunc. I'm new to react jest testing, little confused on how to do it.

1
  • 2
    What did you try? Is there a specific reason why you used addEventListener instead of React events? Commented Nov 25, 2020 at 21:07

1 Answer 1

2

Testing this sort of component can be tricky, but using @testing-library/react I think I was able to come up with something useful.

I did have to make some changes to your component to expose the API a bit, and I also made some changes so that it stops listening to the events on mouseup which may not be the specific event you want.

Here's the modified component:

// MouseDownExample.js
import React, { useEffect, useState } from "react";

export default ({ onMouseMoveWhileDown }) => {
  const [x, setX] = useState(null);
  const [listening, setListening] = useState();

  // Replaced with mouse move function, should make sure we're unlistening as well
  useEffect(() => {
    if (listening) {
      const onMouseMove = (event) => {
        // call some parent function
        onMouseMoveWhileDown(event);

        console.log(event.clientX);

        // purely for testing purposes
        setX(event.clientX);
      };
      const onMouseUp = (event) => {
        // stop listening on mouse up
        // - you should pick whatever event you want to stop listening
        // - this is global so it also stops when the mouse is outside the box
        setListening(false);
      };
      document.addEventListener("mousemove", onMouseMove);
      document.addEventListener("mouseup", onMouseUp);
      return () => {
        document.removeEventListener("mousemove", onMouseMove);
        document.removeEventListener("mouseup", onMouseUp);
      };
    }
  }, [listening, onMouseMoveWhileDown]);

  return (
    <div
      style={{
        backgroundColor: "red",
        width: 200,
        height: 200
      }}
      className="test-div"
      onMouseDown={() => {
        // moved this inline, so no ref
        setListening(true);
      }}
    >
      X Position: {x}
    </div>
  );
};

I called out in comments the main differences.

And here's an example test:

// MouseDownExample.test.js
import React from "react";
import { fireEvent, render } from "@testing-library/react";
import MouseDownExample from "./MouseDownExample";

it("shouldn't trigger onMouseMoveWhileDown when mouse isn't down", () => {
  const onMouseMoveWhileDown = jest.fn();
  const { container } = render(
    <MouseDownExample onMouseMoveWhileDown={onMouseMoveWhileDown} />
  );

  // Note: normally I would use `screen.getByRole` but divs don't have a useful role
  const subject = container.firstChild;

  fireEvent.mouseMove(
    document,
    // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/MouseEvent
    {
      clientX: 200
    }
  );
  // hasn't gone down yet
  expect(onMouseMoveWhileDown).not.toHaveBeenCalled();

  fireEvent.mouseDown(subject);
  fireEvent.mouseUp(subject);

  // went down then up before moving
  fireEvent.mouseMove(document, {
    clientX: 200
  });
  expect(onMouseMoveWhileDown).not.toHaveBeenCalled();
});

it("should trigger onMouseMoveWhileDown when mouse is down", () => {
  const onMouseMoveWhileDown = jest.fn();
  const { container } = render(
    <MouseDownExample onMouseMoveWhileDown={onMouseMoveWhileDown} />
  );

  // Note: normally I would use `screen.getByRole` but divs don't have a useful role
  const subject = container.firstChild;

  fireEvent.mouseDown(subject);
  fireEvent.mouseMove(document, {
    clientX: 200
  });

  expect(onMouseMoveWhileDown).toHaveBeenCalledWith(
    expect.objectContaining({ clientX: 200 })
  );
});

What's happening here, is we're rendering the component, then firing events to ensure the onMouseMoveWhileDown function prop is called when we expect.

We have to do expect.objectContaining rather than just the object because it's called with a MouseEvent which contains other properties.

Another test we might want to add is an unmount test to ensure the listeners are no longer triggering events.

You can look at/experiment with this Code Sandbox with this component and the tests. Hope this helps 👍

Sign up to request clarification or add additional context in comments.

1 Comment

If you have any issues with the modifications made, lmk and we can work that out/try to figure out how to test without the changes

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.