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">×</span>
<p>Some text in the Modal..</p>
</div>
</div>
</div>
);
};
export default Modal;
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>
);
}
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();
});
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")
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)
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"
}
)
);
which will fire event and call closeModal function.
Hope you liked this tutorial.
Top comments (0)