如何用Jest测试与Mock Node.js readline?测试报错求助
问题分析
你的测试失败有两个核心原因:
- 错误抛出时机不匹配:
throw语句是在rl.question的回调函数里执行的,属于异步代码范畴,但expect(() => app.play()).toThrow只能捕获同步执行的函数抛出的错误,回调里的异步错误无法被同步捕获逻辑抓到。 - Mock未真正生效:你在测试中创建的
rl实例和App模块内部初始化的rl不是同一个对象,所以你给测试里的rl赋值mockQuestion,根本不会影响到App里实际使用的rl实例。
修复方案
步骤1:正确Mock readline模块
我们需要让App使用我们Mock出来的readline实例,而非它自身创建的实例。可以用Jest的jest.mock直接mock整个readline模块:
步骤2:处理异步错误的捕获
因为错误是在回调里异步抛出的,必须用Jest的异步测试方式来捕获,比如使用done回调,或者把回调逻辑包装成Promise。
方案一:不修改原App代码的测试写法
const readline = require("readline"); const App = require("../src/App"); // Mock readline模块的createInterface方法,让它返回我们可控的mock实例 jest.mock("readline", () => { const mockRl = { question: jest.fn() }; return { createInterface: jest.fn(() => mockRl) }; }); test("输入4个数字时抛出错误", (done) => { // 获取mock出来的rl实例 const mockRl = readline.createInterface(); // 重写question的mock逻辑,触发回调并监听未捕获错误 mockRl.question.mockImplementation((question, callback) => { // 监听全局未捕获异常,捕获回调里抛出的错误 process.once('uncaughtException', (error) => { expect(error.message).toBe("[ERROR] You have to input 3 numbers"); done(); // 告诉Jest测试已完成 }); callback("1234"); }); const app = new App(); app.play(); });
方案二:修改App代码为Promise风格(更简洁易测)
如果允许调整原业务代码,把play方法改成返回Promise的形式,测试会更直观:
修改后的index.js
const readline = require("readline"); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); class App { play() { return new Promise((resolve, reject) => { rl.question("Input Numbers ", (userInput) => { const userNumbers = userInput.split("").map(Number); if (userNumbers.length !== 3) { reject(new Error("[ERROR] You have to input 3 numbers")); return; } resolve(userNumbers); }); }); } } module.exports = App;
对应的测试代码
const readline = require("readline"); const App = require("../src/App"); jest.mock("readline", () => { const mockRl = { question: jest.fn() }; return { createInterface: jest.fn(() => mockRl) }; }); test("输入4个数字时抛出错误", async () => { const mockRl = readline.createInterface(); mockRl.question.mockImplementation((question, callback) => { callback("1234"); }); const app = new App(); // 用async/await配合rejects断言捕获异步错误 await expect(app.play()).rejects.toThrow("[ERROR] You have to input 3 numbers"); });
关键说明
- Mock模块而非单独实例:用
jest.mock替换整个readline模块的createInterface方法,确保App全程使用我们的mock实例,避免出现实例不匹配的问题。 - 异步错误处理:要么通过监听全局
uncaughtException捕获回调里的错误,要么把异步逻辑包装成Promise,用async/await配合expect().rejects来完成测试。
内容的提问来源于stack exchange,提问作者daechaeknam
相关产品推荐
相关产品推荐

