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(); }); }); });
最后总结
- 先修复API代码的响应逻辑,确保正确解析外部API的JSON响应,并且所有分支都有明确的返回
- 使用Mock工具(如nock)来模拟外部API的各种场景,测试你的集成逻辑是否正确
- 扩展测试用例,覆盖成功、错误、未知命令等所有可能的情况
内容的提问来源于stack exchange,提问作者André Maldonado
相关产品推荐
相关产品推荐

