DEV Community

Krishna Gupta
Krishna Gupta

Posted on

Unit Testing of Events & attach custom DOM Methods with React Testing Library

Last month, I was struggling to find a way to solve some of the complex testing issue related to attach methods on DOM element.

Let's say you have a scenario like you are using any library/framework in your project for displaying Modal and that Modal component is exposing events and methods to handle the functionality in your component.

In this example I have made my own Modal component for better understanding.This component could be made in much easier way but I have tried to make it more complex for making our testing more challenging.

Modal.js

import React, { useEffect } from "react";

const event = new CustomEvent("closeModal", {
  detail: {
    value: "close"
  }
});

const Modal = () => {
  const openModal = () => {
    document.getElementById("myModal").style.display = "block";
  };

  const closeModal = () => {
    document.getElementById("myModal").style.display = "none";
  };

  useEffect(() => {
    document
      .getElementById("myModal")
      .querySelector(".modal-content").openModal = openModal;
    document
      .getElementById("myModal")
      .querySelector(".close").closeModal = closeModal;
    document
      .getElementById("myModal")
      .querySelector(".close").onclick = function () {
      document.dispatchEvent(event);
    };
  }, []);

  return (
    <div data-testid="modal">
      <h2>Modal Example</h2>
      <div id="myModal" className="modal">
        <div className="modal-content">
          <span className="close">&times;</span>
          <p>Some text in the Modal..</p>
        </div>
      </div>
    </div>
  );
};

export default Modal;

Enter fullscreen mode Exit fullscreen mode

So, from above Modal component we are exposing openModal & closeModal methods on some DOM node and also dispatching event when user clicks on cross icon.

App.js

import React, { useEffect } from "react";
import "./styles.css";

import Modal from "./Modal";

export default function App() {
  const handleModalAction = () => {
    document
      .getElementById("myModal")
      .querySelector(".modal-content")
      .openModal();
  };

  const closeModal = () => {
    document.getElementById("myModal").querySelector(".close").closeModal();
  };

  useEffect(() => {
    document.addEventListener("closeModal", closeModal);
    return () => {
      document.removeEventListener("closeModal", closeModal);
    };
  }, []);

  return (
    <div data-testid="app">
      <button
        id="myBtn"
        data-testid="open-modal-btn"
        onClick={handleModalAction}
      >
        Open Modal
      </button>
      <Modal handleModalAction={handleModalAction} />
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Here, in App component we are subscribing to exposed event from Modal Component and handling them on click of Open Modal Button.

Now, when you try to run following test for App component :-

it("Should open Modal when clicked on Open Modal button", () => {
    render(<App />);
    fireEvent.click(screen.getByTestId("open-modal-btn"));
    expect(screen.getByTestId("modal")).toBeInTheDocument();
  });
Enter fullscreen mode Exit fullscreen mode

It will run fine if you are directly using Modal component but if you import it as NPM package, then it fails because render will not consider code inside it, due to which it will not able to find
following DOM node :-

document.getElementById("myModal").querySelector(".modal-content")
Enter fullscreen mode Exit fullscreen mode

and also attach methods.
So, to resolve this we have to spy on document and return mockImplementation of attach methods like below :-

const elementMock = { openModal : jest.fn() }
jest.spyOn(document.getElementById(myModal),'querySelector').mockImplementation(() => elementMock)
Enter fullscreen mode Exit fullscreen mode

and to close Modal we can fire closeModal event, by using fireEvent function of @testing-library/react like below :-

fireEvent(
      document,
      createEvent(
        "closeModal",
        document,
        {
          detail: {
            value: "close"
          }
        },
        {
          EventType: "CustomEvent"
        }
      )
    );
Enter fullscreen mode Exit fullscreen mode

which will fire event and call closeModal function.

Hope you liked this tutorial.

Latest comments (0)