Jest模拟readFileSync在POST接口失效,GET接口正常,求故障原因?
你的POST接口测试中mock失效的核心原因是模块加载顺序和Jest模块缓存的冲突:
在beforeEach钩子中,你先require('fs')并给它的readFileSync打了spy,但之后当你require(appPath)引入Express应用时,jest.resetModules()会清空所有模块的缓存,导致App代码里重新加载了一个全新的fs模块实例——这个实例完全没被你的spyOn处理过,所以POST接口调用readFileSync时,用的是真实的文件系统方法,自然会抛出「No such file or directory」错误。
而GET测试看似正常,本质是巧合(比如测试环境中刚好存在目标文件),但其实也存在相同的潜在问题,只是没暴露出来而已。
修复方案:改用jest.mock全局mock整个fs模块
我们需要让App无论何时加载fs,都拿到同一个mock版本,具体步骤如下:
修改
beforeEach钩子,替换spyOn为全局mock
去掉原来的spyOn操作,直接用jest.mock全局mock整个fs模块,确保所有后续require('fs')都拿到同一个mock对象:beforeEach(() => { jest.resetModules() jest.clearAllMocks() // 全局mock fs模块,提供mock的读写方法 jest.mock('fs', () => ({ readFileSync: jest.fn(), writeFileSync: jest.fn() })) })调整mock方法的实现
更新你的mock配置函数,直接从mock的fs模块中获取方法并设置返回值:function mockReadFileCall(testData){ const fs = require('fs') fs.readFileSync.mockReturnValueOnce(JSON.stringify(testData)) } function mockWriteFileCall(){ const fs = require('fs') fs.writeFileSync.mockImplementation(() => {}) // 模拟写入成功,无实际操作 }确保mock配置在App引入之前完成
保持测试用例的顺序:先配置mock返回值,再引入App,这样App加载fs时就能拿到已配置好的mock方法:describe('create list entry', () => { it('should return 201', async () => { // 1. 先配置mock的初始数据 mockReadFileCall({ "data": [ { "id": 0, "name": "filled shopping list", "location": "lidl", "targetDate": "22.03.1986", "priority": 1, "isFinished": false, "items": [{"count": 1, "name": "vodka"}, {"count": 1, "name": "vodka"} ] }, { "id": 1, "name": "filled shopping list2", "location": "lidl2", "targetDate": "22.03.1986", "priority": 1, "isFinished": false, "items": [{"count": 1, "name": "vodka"}, {"count": 1, "name": "vodka"} ] } ] }) mockWriteFileCall() // 2. 再引入App const {app} = require(appPath) // 3. 构造请求并断言 const entry = { "name": "new shopping list", // 不要传id,接口会自动生成 "location": "lidl3", "targetDate": "22.03.1986", "priority": 1, "isFinished": false, "items": [{"count": 1, "name": "vodka"} ] } const res = await request(app) .post(baseUrl) .send(entry) // 新id是2(初始数据最大id为1),所以Location路径要对应 expect(res.statusCode).toEqual(201) expect(res.body).toEqual({"Location": `${baseUrl}/2`}) }) })
额外小提示
你的POST测试用例中,传入的entry包含了id:1,但接口逻辑会用getNewestId()生成新id,这个传入的id会被覆盖,建议去掉避免混淆;同时断言里的baseUrl + 1要改成baseUrl + '/2',否则断言会失败。
内容的提问来源于stack exchange,提问作者Collin

