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

使用Sinon单元测试Express.js路由遇阻,求解决方案

我完全理解你现在在Express路由单元测试上卡壳的感觉——新手阶段踩这种测试的坑太正常了!咱们一步步拆解问题,把这个测试给跑通。

首先先分析你当前测试报错的原因:你写的rootRouter.route('/').get(null, res)完全用错了Express的API!Route.get()是用来注册路由处理函数的,不是用来触发它执行的,你传个null当回调,Express当然会报错说“需要回调函数”。测试路由的正确姿势是拿到已经注册好的处理函数,给它传入模拟的req和res对象,然后断言res的方法有没有被正确调用。

第一步:先重构路由代码,让它更易测

你的路由里直接用了全局的host和port,这在测试环境里会因为没有赋值而报错,而且全局变量本身就不利于测试。咱们把它改成从req.app.locals取值(刚好你的server.js里也把DB连接挂在了app.locals上,风格统一):

// ./routers/root.js
var express = require('express');
var router = express.Router();

// 把处理函数单独抽出来,方便直接测试
exports.getRootHandler = function(req, res) {
  // 从app.locals获取配置,测试时可以轻松注入测试值
  const { host, port } = req.app.locals;
  res.writeHead(200, {'Content-Type': 'application/json'});
  res.write(JSON.stringify({
    Application: "NodeTours", 
    Version: "2.0.0", 
    Host: host, 
    Port: port
  }));
  res.end();
};

router.route('/').get(exports.getRootHandler);

module.exports = router;

第二步:写正确的单元测试代码

用Sinon模拟req、res对象,跟踪它们的方法调用,然后直接调用我们抽出来的处理函数,最后断言结果是否符合预期:

// ./test/test.js
var sinon = require('sinon')
var expect = require('chai').expect
var { getRootHandler } = require('../routers/root')

describe('Routers', function() {
  context('GET /', function() {
    let mockReq;
    let mockRes;

    // 每个测试前重置模拟对象,避免测试间的污染
    beforeEach(function() {
      // 模拟req对象,注入测试用的host和port
      mockReq = {
        app: {
          locals: {
            host: 'test-localhost',
            port: '8888'
          }
        }
      };
      // 用Sinon stub模拟res的所有方法,方便跟踪调用
      mockRes = {
        writeHead: sinon.stub(),
        write: sinon.stub(),
        end: sinon.stub()
      };
    });

    it('should return 200 status and correct JSON response', function() {
      // 调用路由处理函数
      getRootHandler(mockReq, mockRes);

      // 断言writeHead是否以正确参数被调用
      expect(mockRes.writeHead.calledOnceWith(
        200, 
        {'Content-Type': 'application/json'}
      )).to.be.true;

      // 断言返回的JSON内容是否正确
      const expectedResponseBody = JSON.stringify({
        Application: "NodeTours",
        Version: "2.0.0",
        Host: 'test-localhost',
        Port: '8888'
      });
      expect(mockRes.write.calledOnceWith(expectedResponseBody)).to.be.true;

      // 断言res.end()是否被调用
      expect(mockRes.end.calledOnce).to.be.true;
    });
  });
});

测试逻辑解释

  1. 模拟对象:用Sinon的stub来模拟res的方法,这样我们能跟踪这些方法有没有被调用、传了什么参数。
  2. 直接调用处理函数:因为我们把处理函数单独导出了,不用再从路由栈里找,直接调用更简单。
  3. 断言验证:分别验证状态码、响应头、响应体、结束响应这几个关键步骤是否符合预期。

额外小建议

如果你不想重构路由代码,也可以从Express Router的stack数组里找到对应的处理函数(不过不如抽出来直观):

// 在测试里获取处理函数的方式
const getHandler = rootRouter.stack.find(layer => 
  layer.route && 
  layer.route.path === '/' && 
  layer.route.methods.get
).handle;

内容的提问来源于stack exchange,提问作者Przemek

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 13:27:29