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

Node.js中测试调用外部API的集成接口遇异步响应问题求助

解决Mocha测试中API提前响应及集成测试的疑问

首先,咱们先定位你测试失败的核心原因:

你的测试报错expected {} to have property 'success',本质是你的API返回的响应体没有被chai-http正确解析为JSON对象,导致res.body变成了空对象。这是因为你直接把Habitica API返回的原始字符串通过res.send(apiBody)发送了,而Express没有自动将其解析为JSON,同时chai-http默认尝试按JSON解析,最终解析失败得到空对象。

另外,你的API代码还有一个潜在问题:如果req.body不存在,没有任何响应逻辑,会导致请求超时,这也是测试不稳定的隐患。

第一步:修复API代码的响应逻辑

咱们先把API代码改得更健壮,确保正确处理外部API的响应,并且所有分支都有明确的响应:

const request = require('request');
// 替换为Habitica任务API的实际URL
const GET_TASKS = 'https://habitica.com/api/v3/tasks/user';

app.post("/habitica", server.urlencodedParser, function(req, res) {
  // 先校验请求体是否存在,不存在直接返回错误
  if (!req.body) {
    return res.status(400).send({
      "success": "false",
      "message": "Missing request body"
    });
  }

  switch(req.body.text) {
    case "list":
      request({
        url: GET_TASKS,
        headers: {
          "x-api-user": process.env.HABITICA_USERID,
          "x-api-key": process.env.HABITICA_APITOKEN
        },
        json: true // 让request自动将响应解析为JSON对象
      }, function (apiError, apiResponse, apiBody) {
        if (apiError) {
          // 统一错误响应格式,避免直接返回原始错误对象
          return res.status(500).send({
            "success": "false",
            "message": "Failed to fetch tasks from Habitica",
            "error": apiError.message
          });
        } 
        // 校验Habitica的响应状态码
        if (apiResponse.statusCode === 200) {
          // 按你的API格式包装响应
          res.send({
            "success": "true",
            "tasks": apiBody.data
          });
        } else {
          res.status(apiResponse.statusCode).send({
            "success": "false",
            "message": "Habitica API returned an error",
            "habiticaError": apiBody
          });
        }
      });
      break;
    default:
      res.send({
        "success": "false",
        "message": "Still working on tasks creation"
      });
  }
});

第二步:优化测试代码,引入Mock解决集成测试问题

针对你提到的两个疑问,咱们逐个拆解:

疑问1:要不要测试非自有代码?

你完全不需要测试Habitica API本身的正确性——那是Habitica团队的职责。你要测试的是你的集成逻辑:

  • 当收到text: "list"的请求时,你的API是否正确调用Habitica的API(请求URL、头信息是否正确)
  • 当Habitica返回成功/失败时,你的API是否返回符合预期格式的响应
  • 当收到未知命令时,你的API是否返回正确的提示

疑问2:Mock外部API会不会失去集成测试的意义?

恰恰相反,Mock是让集成测试变得可控、稳定、全面的关键:

  • 避免依赖外部服务的可用性(比如Habitica API限流、宕机)
  • 可以模拟各种边界场景(比如Habitica返回500错误、超时、空任务列表)
  • 测试速度更快,不需要等待真实网络请求

咱们用nock这个工具来Mock Habitica的API,修改测试代码如下:

首先安装依赖:

npm install --save-dev nock

然后编写测试用例:

var chai = require("chai");
var chaiHttp = require("chai-http");
var nock = require('nock');
var server = require("../src/app/index");
var should = chai.should();

chai.use(chaiHttp);

describe("/POST habitica", () => {
  // 每次测试前清理所有Mock拦截
  beforeEach(() => nock.cleanAll());

  it("should return user's tasks when receiving 'list' command", (done) => {
    // Mock Habitica的成功响应
    const habiticaMock = nock('https://habitica.com')
      .get('/api/v3/tasks/user')
      // 验证请求头是否正确
      .matchHeader('x-api-user', process.env.HABITICA_USERID)
      .matchHeader('x-api-key', process.env.HABITICA_APITOKEN)
      .reply(200, {
        data: [
          { id: 'task-1', text: 'Finish API test' },
          { id: 'task-2', text: 'Fix integration bug' }
        ]
      });

    chai.request(server)
      .post("/habitica")
      .type("urlencoded")
      .send({text: "list"})
      .end((err, res) => {
        res.should.have.status(200);
        res.body.should.be.an("object");
        res.body.should.have.property("success").eql("true");
        res.body.should.have.property("tasks").that.is.an('array').with.lengthOf(2);
        
        // 验证Mock确实被调用了,确保你的API真的发起了请求
        habiticaMock.done();
        done();
      });
  });

  it("should return error when Habitica API fails", (done) => {
    // Mock Habitica的错误响应
    const habiticaMock = nock('https://habitica.com')
      .get('/api/v3/tasks/user')
      .reply(500, { error: 'Internal Server Error' });

    chai.request(server)
      .post("/habitica")
      .type("urlencoded")
      .send({text: "list"})
      .end((err, res) => {
        res.should.have.status(500);
        res.body.should.have.property("success").eql("false");
        res.body.should.have.property("message").eql("Failed to fetch tasks from Habitica");
        
        habiticaMock.done();
        done();
      });
  });

  it("should return error for unknown commands", (done) => {
    chai.request(server)
      .post("/habitica")
      .type("urlencoded")
      .send({text: "create-task"})
      .end((err, res) => {
        res.should.have.status(200);
        res.body.should.have.property("success").eql("false");
        res.body.should.have.property("message").eql("Still working on tasks creation");
        done();
      });
  });
});

最后总结

  1. 先修复API代码的响应逻辑,确保正确解析外部API的JSON响应,并且所有分支都有明确的返回
  2. 使用Mock工具(如nock)来模拟外部API的各种场景,测试你的集成逻辑是否正确
  3. 扩展测试用例,覆盖成功、错误、未知命令等所有可能的情况

内容的提问来源于stack exchange,提问作者André Maldonado

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:03:27