You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 03:53:13