【问题标题】:Sinon spy not triggering诗乃间谍未触发
【发布时间】:2025-12-05 15:25:01
【问题描述】:

我在追查为什么一个 sinon 间谍没有被触发时遇到了麻烦。在下面的测试中,两个控制台语句都报告为 false,因此没有调用任何方法(以防出现错误)。

这是我的一个 mocha 测试通常的样子:

describe('Post Controller', function () {
    var controller = require('../controllers/PostController'),
    req = {
        user: {
            check_id: "00100",
            access_id: "54876329"
        }
    };

beforeEach(function () {
    res = {
        send: sinon.spy(),
        json: sinon.spy()
    };
});

afterEach(function () {
    res.send.reset();

});

describe('readAllPosts', function () {
    it('should return an array of posts', function (done) {
        controller.readAllPosts(req, res);
        process.nextTick(function () {
            console.log('send: ', res.send.called);
            console.log('json: ', res.json.called);
            assert(res.json.calledWith(sinon.match.array));
            done();
        });
    });
});

});

PostContoller 中的readAllPosts 方法:

var postController = {
    readAllPosts: function (req, res) {
        PostModel.find(
            {
                access_id: req.user.access_id
            },
            function (err, results) {
                if (err) {
                    res.send(404, err);
                } else {
                    // res here is a spy
                    res.json(results);
                }
            }
        );
    }
};

最后是 PostModel 中的 find 方法:

Posts.find= function (conditions, cb) {
    /* ... */
    dbQuery(query, function (err, results) {
        if (err) {
            cb(err);
        }

        cb(null, results);
    });
};

如果我以正常方式调用方法,它们会执行 find,返回预期的 Posts 数组。然而,res 间谍永远不会被执行。另外,如果我将控制器类中的方法更改为此

var postController = {
    readAllPosts: function (req, res) {
        res.json([{this:"that",and:"other"}]);
    }
};

间谍功能 (res) 会触发。我知道正在调用发送到模型的回调,并且 res 对象在那里但没有被调用。我不认为这是一个间接问题(调用原始方法而不是 sinon 包装的方法),但我不确定问题出在哪里。

【问题讨论】:

    标签: javascript unit-testing sinon


    【解决方案1】:

    用 Sinon 间谍/存根测试异步函数很困难,因为 (AFAIK) 你无法让一个告诉你它什么时候被调用(它甚至可能根本不会被调用)。

    在您的情况下,您假设 PostModel.find() 将在下一个滴答声中完成(根据您对 process.nextTick() 的使用判断),这很可能不是这种情况,因此您正在检查您的间谍,当查询仍在运行,尚未调用任何间谍。

    在我看来,您还试图通过一项测试进行太多测试:PostModel.find()dbQuery()controller.readAllPosts()。我可能会在不同的测试中将它们分开,并将其余的存根。

    例如,如果您想检查您的控制器是否发回正确的响应,或者它是否正确处理错误,请存根PostModel.find() 让它(同步)调用带有错误或正确结果数组的回调,然后检查res.send/res.json 间谍。

    另一种选择是不使用间谍,而是使用普通回调。例如:

    controller.readAllPosts(req, {
      send : function(data) {
        assert(data, ...);
        done();
      },
      json : function(data) {
        assert(data, ...);
        done();
      },
    });
    

    (或其变体)

    但是,我不喜欢这样做,因为它会阻止您测试(以一种简单的方式)例如,没有两个这两个方法都被调用。

    【讨论】:

    • 最终我正在尝试测试控制器。我有直接针对模型的测试(未显示)并直接测试 API 端点。我已经为控制器运行了几个不同的变体,但我不确定如何测试它。我对 sinon 还很陌生,所以我很确定我做错了:)