Code that changes the DOM can be tricky and easily lead to bugs. This lesson shows how to verify UI logic with jsdom and lightweight simulations. The core is “jsdom + user scenario tests,” while BrowserMock is an optional helper.
Key terms
- jsdom: A library that emulates DOM APIs inside Node.js so you can run UI tests without a browser.
- User scenario test: Recreates what users click or type and checks the resulting UI.
- data-testid: A
data-attribute that gives tests stable selectors. - BrowserMock: A lightweight tool that spins up a fake browser from plain HTML strings.
- CI (Continuous Integration): Automates tests before code merges.
Core ideas
- jsdom (Node-friendly DOM): Lets Node.js mimic browser APIs for render functions and event handlers.
- User scenario test (replay user behavior): Script real interactions such as button clicks and form inputs.
- BrowserMock/virtual DOM mock (HTML simulator): Helps you inspect HTML without a real browser.
- Test hook data attribute (stable selector): Attributes like
data-testidmake element queries reliable.
Code examples
Install the tools that will provide our test environment. Before that, feel the pain point you are solving.
renderGreeting(root, "Minji");
console.log(root.innerHTML);
➡️ If “manually checking the console every time” annoyed you, you already see why automated tests matter.
npm install --save-dev vitest jsdom
Create a simple render function to give the tests a target.
// greet.js
export function renderGreeting(root, name) {
root.innerHTML = `<p data-testid="greeting">Hello, ${name}</p>`;
}
Use jsdom to fake the DOM and verify the function.
// greet.test.js
describe("renderGreeting", () => {
it("shows a greeting with the name", () => {
const dom = new JSDOM(`<!doctype html><div id="root"></div>`);
const root = dom.window.document.getElementById("root");
renderGreeting(root, "Minji");
const text = root.querySelector('[data-testid="greeting"]').textContent;
expect(text).toBe("Hello, Minji");
});
});
jsdom lets you manipulate DOM APIs in Node.js—think “handle the DOM without opening a browser.”
Now add a counter component with events.
// counter.js
export function initCounter(root) {
let value = 0;
root.innerHTML = `
<div>
<output data-testid="value">0</output>
<button data-testid="inc">+</button>
</div>
`;
root.querySelector('[data-testid="inc"]').addEventListener("click", () => {
value += 1;
root.querySelector('[data-testid="value"]').textContent = String(value);
});
}
Recreate the user scenario by firing events yourself.
// counter.test.js
describe("initCounter", () => {
it("increments the value when the button is clicked", () => {
const dom = new JSDOM(`<!doctype html><div id="app"></div>`);
const app = dom.window.document.getElementById("app");
initCounter(app);
const button = app.querySelector('[data-testid="inc"]');
const value = app.querySelector('[data-testid="value"]');
button.dispatchEvent(new dom.window.Event("click"));
expect(value.textContent).toBe("1");
});
});
This is the entire idea of a scenario test: “pretend a button was clicked and assert the value changed.”
BrowserMock is a lightweight option for quickly sketching HTML without a formal test runner—treat it as an optional helper.
// BrowserMock example
const { document, window } = mock("<form><input name=\"title\" /></form>");
const input = document.querySelector("input");
input.value = "Prep meeting";
document.querySelector("form").dispatchEvent(new window.Event("submit"));
BrowserMock builds a fake DOM from a string so setup is nearly instant.
Think of the BrowserMock workflow as:
- Create an HTML snippet as a string.
- Run the initialization function.
- Fire events that mimic user actions.
- Inspect
document.body.innerHTMLor any node’s text content.
💬 댓글
이 글에 대한 의견을 남겨주세요