本地双Express服务跨端口调用:chai-http测试时Axios请求被拒绝
解决测试Express服务A时调用服务B出现
ECONNREFUSED的问题 你遇到的connect ECONNREFUSED 127.0.0.1:3020错误,本质是测试运行时服务B根本没启动(或者端口配置不匹配)——毕竟Postman能正常访问服务B,说明服务B本身的逻辑和接口是没问题的。我帮你拆解几个最可能的原因和解决办法:
1. 先查服务B的启动逻辑(最可能的坑)
看你服务B的listen函数里有一行注释掉的代码:if (app.get('env') === 'test') return;,如果你的测试环境把NODE_ENV设成了test,哪怕你没打开这行注释,也有可能测试框架或配置让服务B跳过了端口监听。
解决办法:
- 直接去掉这个环境判断,或者修改逻辑,让测试环境下服务B也能启动监听端口;
- 或者在测试脚本里显式启动服务B的实例,而不是依赖单独运行的服务。比如在测试文件的前置钩子中启动服务B:
const appB = require('../path/to/serviceB/app'); let serverB; // 测试前启动服务B before((done) => { serverB = appB.listen(3020, () => { console.log('测试用服务B已启动在3020端口'); done(); }); }); // 测试结束后关闭服务B after((done) => { serverB.close(done); });
2. 检查测试环境的变量配置
服务A里用process.env.userapi拼接请求地址,测试时这个环境变量可能没正确加载:
- 要么是测试环境没加载对应的
.env文件(比如你用了dotenv但没指定测试环境的配置文件); - 要么是
userapi的值和服务B的测试端口不匹配。
解决办法:
- 在测试文件开头加载测试专用的环境变量:
require('dotenv').config({ path: '.env.test' }); - 或者手动在测试代码里指定变量:
process.env.userapi = 'http://localhost:3020';
3. 测试执行顺序的时序问题
有时候测试代码跑得太快,服务B还没完全启动就绪,服务A就发起了请求,导致连接被拒绝。
解决办法:
- 用测试框架的
before/beforeEach钩子确保服务B完全启动后再执行测试(上面的例子已经包含了这种处理); - 如果是异步启动,可以用Promise或者回调函数等待服务启动完成的信号。
4. 跨域配置的小优化(非直接原因,但可以统一)
你服务B的跨域配置里重复设置了Access-Control-Allow-Origin,虽然这不是导致连接拒绝的原因,但可以统一成一次设置,避免冗余:
res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Credentials', true); res.setHeader( 'Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization, appid' ); res.setHeader( 'Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS' ); if (req.method == 'OPTIONS') { res.status(200).end(); } else { next(); }
完整测试示例(Mocha + Chai-HTTP)
给你一个完整的测试流程参考,确保服务A和服务B在测试中协同运行:
const chai = require('chai'); const chaiHttp = require('chai-http'); const appA = require('../serviceA/app'); const appB = require('../serviceB/app'); require('dotenv').config({ path: '.env.test' }); chai.use(chaiHttp); const expect = chai.expect; let serverB; before((done) => { // 启动服务B serverB = appB.listen(3020, done); }); after((done) => { // 关闭服务B serverB.close(done); }); describe('服务A - 调用服务B邀请接口', () => { it('应该成功调用服务B的/invite接口', (done) => { chai.request(appA) .post('/service-a-invite-endpoint') // 服务A中触发调用B的接口 .send({ email: 'test@example.com' }) .end((err, res) => { expect(err).to.be.null; expect(res).to.have.status(200); // 可以根据实际需求添加更多断言 done(); }); }); });
核心思路就是:测试环境中必须确保服务B处于运行状态,并且服务A能正确访问到它的地址,优先检查服务B的启动逻辑和环境变量配置,这两个是最容易踩的坑。
内容的提问来源于stack exchange,提问作者Guillaume Badi
相关产品推荐
相关产品推荐

