断言 sinon 中的递归调用次数

Assert number of recursive calls in sinon

我有以下队列消费者class,它通过承诺递归运行:

"use strict";

var queue = require("./queue"),
    helpers = require("./helpers"),
    vendors = require("../config/vendors"),
    queueConf = require("../config/queue");

function Consumer() {
    this.queue =  new queue.TaskQueue();
    this.currentItem = null;
    this.port = null;
    this.payload = null;
}

Consumer.prototype.payloadSuccessCb = function (data) {
    this.payload = data;
    this.run();
};

Consumer.prototype.failureCb = function (data) {
    console.error(data);
    throw new Error(data);
    //TODO: Continue queue processing despite the error
};

Consumer.prototype.processItem = function (data) {
    this.currentItem = data;
    process.send("Proccess " +  process.pid + " is processing item " + this.currentItem);
    helpers.getPayload(this.currentItem).then(this.payloadSuccessCb, this.failureCb);
};

Consumer.prototype.wait = function () {
    var self = this;
    process.send("Proccess " + process.pid + " is waiting for new items");
    setTimeout(function () {
        self.run();
    }, queueConf.waitTime);
};

Consumer.prototype.queueSuccessFb = function (data) {
    console.error("here");
    if (data) {
        this.processItem(data);
    } else {
        this.wait();
    }
};

Consumer.prototype.run = function () {
    //this.port = helpers.getVendorPortById(this.currentItem);
    this.queue.pop().then(this.queueSuccessFb, this.failureCb);
};

exports.Consumer = Consumer;

我已经定义了一个测试,它基本上断言正确的工作流正在发生,并且消费者最终处理队列中的所有任务(这是在真正的 Redis 代理前面工作的集成测试)

测试:

"use strict";

var consumer = require("./../../src/consumer"),
    queue = require("./../../src/queue"),
    Q = require("Q"),
    sinon = require("sinon"),
    assert = require("assert"),
    queueConf = require("./../../config/queue"),
    NUM_OF_ITEMS = 5,
    queueInstance,
    spy,
    consumerInstance;

describe("consumer", function () {
    beforeEach(function () {
        queueInstance = new queue.TaskQueue();
    });


    describe("waiting for tasks while the queue is empty", function () {
        describe("queue success call back", function () {
            before(function () {
                consumerInstance = new consumer.Consumer();
                spy = sinon.spy(consumerInstance, "queueSuccessFb");
            });

            it("should call the success callback once per the defined period", function (done) {
                consumerInstance.run();
                setTimeout(function () {
                    sinon.assert.calledOnce(spy);
                    done();
                }, queueConf.waitTime);
            });

            it("should call the success callback twice per the defined period + 1", function (done) {
                consumerInstance.run();
                setTimeout(function () {
                    sinon.assert.calledTwice(spy);
                    done();
                }, queueConf.waitTime * 2);
            });
        });

        describe("wait function", function () {
            before(function () {
                consumerInstance = new consumer.Consumer();
                spy = sinon.spy(consumerInstance, "wait");
            });
        });

    });

    describe("task handling", function () {
        beforeEach(function (done) {
            this.timeout(6000);
            var i, promises = [];
            queueInstance = new queue.TaskQueue();
            for (i = 1; i <= NUM_OF_ITEMS; i += 1) {
                promises.push(queueInstance.push(i));
            }
            Q.all(promises).then(function () {
                done();
            });

        });

        afterEach(function () {
            queueInstance.empty();
        });

        describe("sucess callback", function () {
            before(function () {
                consumerInstance = new consumer.Consumer();
                spy = sinon.spy(consumerInstance, "queueSuccessFb");
            });
            it("should run all of the available tasks one by one", function (done) {
                this.timeout(6000);
                consumerInstance.run();
                setTimeout(function () {
                    console.info(spy.callCount);
                    assert(spy.callCount === NUM_OF_ITEMS);
                    done();
                }, 2000);
            });
        });
    });
});

我的问题是调用计数总是等于 1。 起初我以为需要调用 andCallThrough() 方法,类似于 Jasmine 中的工作方式,但后来发现正在调用实际函数。

尝试使用 sinon.useFakeTimers() 但根本不起作用(测试似乎没有等待,消费者超时 class 没有触发);

预期行为:callCountNUM_OF_ITEMS 相同(通过递归调用)。 实际行为:callCount 始终为 1。

您好,有点难以理解您的队列 class 在做什么。是单例吗?

如果它是 不是 一个单例,您的消费者将在构造时使用一个新的空队列进行初始化。

function Consumer() {
   this.queue =  new queue.TaskQueue();
   ...
}
...
    describe("success callback", function () {
        before(function () {
            consumerInstance = new consumer.Consumer();
            spy = sinon.spy(consumerInstance, "queueSuccessFb");
        });
        ....

这与您在

中创建的队列不同
describe("task handling", function () {
    beforeEach(function (done) {
       ...
       queueInstance = new queue.TaskQueue();
       ...
    });
    ...

因为队列 相同 spy.callCount !== NUM_OF_ITEMS

当然除非是单例,即:

new queue.TaskQueue() === new queue.TaskQueue();

我的建议是使 TaskQueue 能够提供给 Consumer 构造函数,这样您就知道 Consumer 正在对预期的队列进行操作

function Consumer(queue) {
   this.queue = queue;
   ...
}

describe("task handling", function () {
    beforeEach(function (done) {
       ...
       this.queueInstance = new queue.TaskQueue();
       ...
    });
    ...

    describe("success callback", function () {
        before(function () {
            consumerInstance = new consumer.Consumer(this.queueInstance);
            spy = sinon.spy(consumerInstance, "queueSuccessFb");
        });
        ....