如何使用Jest和SuperTest测试带文件上传的Node.js POST接口?
问题:如何在Jest+SuperTest测试中上传文件到POST接口?
我有一个基于Node.js和Handlebars的项目,用Jest和SuperTest做测试。现在要测试POST接口/new-team,这个接口用upload.single('shield')处理单文件上传,并且会用req.file.filename生成crestUrl。目前测试里只能传请求体数据,不知道怎么传文件,导致拿不到req.file.filename。
后端接口代码:
app.post('/new-team', upload.single('shield'), (req, res) => { const teams = JSON.parse(fs.readFileSync('./data/teams.db.json')); const { name, tla, country, address, website, founded, } = req.body; const state = teams.find((team) => team.tla === tla.toUpperCase()); if (state) { res.render('new-team', { layout: 'main', data: { error: 'Ops! The team you want to create has an existing TLA, try it again', }, }); } else { const newTeam = { area: { name: country, }, name, tla: tla.toUpperCase(), country, crestUrl: `/shields/${req.file.filename}`, // 此处需要req.file address, website, founded, }; teams.push(newTeam); fs.writeFile('./data/teams.db.json', JSON.stringify(teams), (err) => { res.status(200).json({ status: 'success', data: { teams, }, }); }); res.redirect('/team-created'); } res.render('new-team', { layout: 'main', }); });
当前测试代码:
test('POST /new-team', async () => { const newTeam = { name: 'rosario central', tla: 'CARC', country: 'Argentina', address: 'colombress 1245', website: 'www.central.com', founded: '1886', }; const response = await request(baseURL).post('/new-team').send(newTeam); expect(response.status).toBe(200); });
解决方案
SuperTest内置了.attach()方法模拟文件上传,同时需要用.field()传递表单字段(因为文件上传请求是multipart/form-data类型,不能用.send()传普通JSON),具体实现如下:
1. 修改测试代码,添加文件上传逻辑
const fs = require('fs'); const path = require('path'); test('POST /new-team with file upload', async () => { // 定义表单字段 const newTeamFields = { name: 'rosario central', tla: 'CARC', country: 'Argentina', address: 'colombress 1245', website: 'www.central.com', founded: '1886', }; // 方式一:使用本地测试文件 const testShieldPath = path.join(__dirname, './test-shield.png'); // 替换为你的测试文件路径 // 方式二:用Buffer模拟文件(无需本地文件) // const testFileBuffer = Buffer.from('fake image content', 'utf-8'); // 构建请求 const requestInstance = request(baseURL).post('/new-team'); // 逐个添加表单字段 Object.entries(newTeamFields).forEach(([key, value]) => { requestInstance.field(key, value); }); // 附加文件,第一个参数对应upload.single('shield')中的字段名'shield' requestInstance.attach('shield', fs.createReadStream(testShieldPath)); // 若用Buffer:requestInstance.attach('shield', testFileBuffer, 'test-shield.png'); // 发送请求并断言 const response = await requestInstance; expect(response.status).toBe(200); // 可选:断言crestUrl是否正确生成 // const createdTeam = response.body.data.teams.find(t => t.tla === 'CARC'); // expect(createdTeam.crestUrl).toMatch(/\/shields\/.+/); });
2. 关键注意事项
- 不要混用
.send()和.attach():.attach()会自动将请求类型设为multipart/form-data,.send()会导致请求格式冲突。 - 测试隔离:每次测试前建议重置
teams.db.json(比如复制一份测试专用的初始文件),避免重复TLA导致测试失败。 - 修复后端异步问题:你的原代码中
fs.writeFile是异步操作,但后续直接调用res.redirect会导致响应提前发送,建议改成异步/await写法:
app.post('/new-team', upload.single('shield'), async (req, res) => { try { const teams = JSON.parse(fs.readFileSync('./data/teams.db.json')); const { name, tla, country, address, website, founded } = req.body; const existingTeam = teams.find(team => team.tla === tla.toUpperCase()); if (existingTeam) { return res.render('new-team', { layout: 'main', data: { error: 'Ops! The team you want to create has an existing TLA, try it again' }, }); } const newTeam = { area: { name: country }, name, tla: tla.toUpperCase(), country, crestUrl: `/shields/${req.file.filename}`, address, website, founded, }; teams.push(newTeam); await fs.promises.writeFile('./data/teams.db.json', JSON.stringify(teams)); res.redirect('/team-created'); } catch (err) { console.error(err); res.status(500).render('new-team', { layout: 'main', data: { error: 'Server error, please try again' }, }); } });
内容的提问来源于stack exchange,提问作者GGrop
相关产品推荐
相关产品推荐

