JSDOM UI集成测试无法获取ElementID预期值:罗马数字转换器测试失败求助
问题描述
我是编程新手,正在完成作业,使用JSDOM进行UI集成测试,测试对象为阿拉伯数字转旧/现代罗马数字的转换器。执行npx jest时测试失败:提交表单后,id为modern-roman-result的元素文本内容为空,预期应为CXXIII。尝试过直接调用API、导入JS文件函数等方法均未解决,相关代码如下:
romanNumerals.html文件
<!DOCTYPE html> <html lang="en"> <head> <link rel="stylesheet" href="romanNumerals.css"> <script src="romanNumerals.js" defer></script> </head> <body> <div class="results"> <div class="result"> <div id="old-roman-result"></div> "Old" Roman Numeral </div> <div class="result"> <div id="modern-roman-result"></div> "Modern" Roman Numeral </div> </div> <form id="arabic-number-form"> <label> Arabic number (1-3999) <input type="number" min="1" max="3999" id="arabic-number" name="arabic-number" /> </label> <button>Convert to "modern" Roman</button> </form> </body> </html>
romanNumerals.js文件
/** * This function initializes the JavaScript functionality after the HTML content is loaded. */ window.addEventListener('DOMContentLoaded', () => { const arabicNumberInput = document.getElementById("arabic-number"); const form = document.getElementById("arabic-number-form"); const oldRomanResult = document.getElementById("old-roman-result"); const modernRomanResult = document.getElementById("modern-roman-result"); /* * Clear Arabic number input field when the page first loads. */ arabicNumberInput.value = ""; /* * Add a listener to the Arabic number input field to listen for typing and * update the "old" Roman numeral result "live" as the user types. If the * value input by the user can't be converted to an "old" Roman numeral, * don't display any value. */ arabicNumberInput.addEventListener("input", function () { modernRomanResult.textContent = ""; const arabicNumber = parseInt(arabicNumberInput.value); try { oldRomanResult.textContent = convertToOldRoman(arabicNumber); } catch (err) { oldRomanResult.textContent = ""; } }); /* * When the user actually submits the form, send a request to the API described * here to convert the number to a "modern" Roman numeral: * * https://helloacm.com/tools/romans/ * * If an error occurs in the translation, an error message is displayed. */ form.addEventListener("submit", async function (event) { event.preventDefault(); clearErrorMessages(); const arabicNumber = arabicNumberInput.value; try { const response = await fetch( `https://romans.justyy.workers.dev/api/romans/?n=${arabicNumber}` ); const data = await response.json(); if (data.error) { displayError(data.error); } else { modernRomanResult.textContent = data.result; } } catch (err) { displayError(err.message); } }); /* * This function displays an error message to the user if the translation * request failed for any reason. */ function displayError(errorMsg) { const errorDiv = document.createElement("div"); errorDiv.classList.add("error"); errorDiv.setAttribute("role", "alert"); errorDiv.innerHTML = "<h3>❌ Error</h3>"; /* * The error message may have come from the outside world, so we have to * treat it cautiously, making sure it's not executed as HTML code. */ const errorMsgP = document.createElement("p"); errorMsgP.textContent = errorMsg; errorDiv.appendChild(errorMsgP); form.appendChild(errorDiv); } /* * This function removes any error message currently being displayed to the * user. */ function clearErrorMessages() { const errors = form.querySelectorAll(".error"); errors.forEach(function (error) { error.remove(); }); } /* * This table represents the conversions between Roman digits and Arabic values. */ const conversionTable = { "M": 1000, "C": 100, "L": 50, "X": 10, "V": 5, "I": 1 }; /** * This function converts an Arabic number into the corresponding "old" Roman * numeral. Old Roman numerals are based only on addition. For example, the * Arabic number 9 is "VIIII" in old Roman numerals (it's "IX" in modern Roman * numerals). * * @param {number} input A numeric value representing the Arabic number to * convert to old Roman numerals. Must be in the range 1-3999. Any decimal * part of the number is ignored. * * @throws {RangeError} Throws a RangeError if the input value is a number * outside the range 1-3999. * @throws {Error} Throws an Error if the input is not a number. * * @returns {string} Returns a string containing the old Roman numeral * conversion for the specified input value. */ function convertToOldRoman(input) { if (input === undefined || typeof input !== "number") { throw Error("Expected a number parameter"); } if (input < 1 || input > 3999) { throw RangeError("Input must be in range 1-3999"); } /* * Cycle through Roman digits from greatest (M) to least (I). For each * digit, subtract the corresponding Arabic value from the input number * as many times as possible, adding an instance of the current Roman * to the resultint translation for each subtraction. */ const romanDigits = Object.keys(conversionTable); let currRomanIdx = 0; let result = ""; while (input > 0 && currRomanIdx < romanDigits.length) { const currRomanDigit = romanDigits[currRomanIdx]; const currArabicValue = conversionTable[currRomanDigit]; while (input >= currArabicValue) { result += currRomanDigit; input -= currArabicValue; } currRomanIdx++; } return result; } });
romanNumerals.test.js文件
/** * @jest-environment jsdom */ // Polyfill TextEncoder global.TextEncoder = require('util').TextEncoder; global.TextDecoder = require('util').TextDecoder; const fs = require("fs"); const { JSDOM } = require("jsdom"); const { fireEvent, waitFor } = require("@testing-library/dom"); const userEvent = require("@testing-library/user-event").default; const axios = require("axios"); const MockAdapter = require("axios-mock-adapter"); // Load HTML content const htmlContent = fs.readFileSync("./romanNumerals/romanNumerals.html", "utf8"); const { document } = new JSDOM(htmlContent).window; // Mock the API response for conversion to "modern" Roman numerals const mock = new MockAdapter(axios); mock.onGet("https://romans.justyy.workers.dev/api/romans/?n=123").reply(200, { result: "CXXIII" }); function initDomFromFiles() { document.body.innerHTML = htmlContent; require("./romanNumerals.js"); } test("should update 'modern' Roman numeral result on form submission", async () => { // Arrange initDomFromFiles(); const arabicNumberInput = document.getElementById("arabic-number"); const convertButton = document.querySelector("button"); const modernRomanResult = document.getElementById("modern-roman-result"); // Act: const user = userEvent.setup(); await user.type(arabicNumberInput, "123"); await user.click(convertButton); // Assert await waitFor(() => { expect(document.getElementById("modern-roman-result").textContent).toBe("CXXIII"); }); });
问题原因与修复方案
核心问题
你的测试代码存在两个关键问题:
- Fetch请求未被正确Mock:业务代码用的是原生
fetchAPI,但测试中错误使用了axios-mock-adapter去Mock axios请求,两者不匹配,导致真实请求被JSDOM拦截,无法获取预期数据。 - DOM初始化事件未触发:JSDOM环境下
DOMContentLoaded事件不会自动触发,导致JS文件中的事件监听器没有绑定到DOM元素上。
修复步骤
1. 替换为Mock原生Fetch API
删除测试文件中所有axios相关代码,直接Mock全局fetch函数:
// 在测试文件顶部添加 global.fetch = jest.fn(() => Promise.resolve({ json: () => Promise.resolve({ result: "CXXIII" }), }) );
2. 手动触发DOMContentLoaded事件
修改initDomFromFiles函数,手动触发事件确保JS初始化逻辑执行:
function initDomFromFiles() { document.body.innerHTML = htmlContent; require("./romanNumerals.js"); // 手动触发DOMContentLoaded事件 window.dispatchEvent(new Event('DOMContentLoaded')); }
3. 修复后的完整测试文件
/** * @jest-environment jsdom */ // Polyfill TextEncoder/Decoder global.TextEncoder = require('util').TextEncoder; global.TextDecoder = require('util').TextDecoder; const fs = require("fs"); const { waitFor } = require("@testing-library/dom"); const userEvent = require("@testing-library/user-event").default; // Load HTML content const htmlContent = fs.readFileSync("./romanNumerals/romanNumerals.html", "utf8"); const { document, window } = new JSDOM(htmlContent).window; // Mock Fetch API global.fetch = jest.fn(() => Promise.resolve({ json: () => Promise.resolve({ result: "CXXIII" }), }) ); function initDomFromFiles() { document.body.innerHTML = htmlContent; require("./romanNumerals.js"); // 手动触发DOMContentLoaded事件 window.dispatchEvent(new Event('DOMContentLoaded')); } test("should update 'modern' Roman numeral result on form submission", async () => { // Arrange initDomFromFiles(); const arabicNumberInput = document.getElementById("arabic-number"); const convertButton = document.querySelector("button"); // Act const user = userEvent.setup(); await user.type(arabicNumberInput, "123"); await user.click(convertButton); // Assert const modernRomanResult = await waitFor(() => document.getElementById("modern-roman-result")); expect(modernRomanResult.textContent).toBe("CXXIII"); });
验证修复
执行npx jest,测试即可通过:
- Fetch请求被正确Mock,返回预期的
CXXIII DOMContentLoaded事件触发后,表单提交监听器正常绑定- 用户交互完成后,结果元素能正确更新文本内容
内容的提问来源于stack exchange,提问作者Domanik Logan
相关产品推荐
相关产品推荐

