【问题标题】:Stubbing a function in sinon to return different value every time it is called在 sinon 中存根函数以在每次调用时返回不同的值
【发布时间】:2015-07-24 00:13:11
【问题描述】:

我有一个功能如下图:

function test(parms) {
        var self = this;
        return this.test2(parms)
        .then(function (data) {
            if (data) {
                return ;
            }
            else {
                return Bluebird.delay(1000)
                .then(self.test.bind(self, parms));
            }
        }.bind(self));
    };

我正在尝试为此功能编写单元测试。我正在使用sinon.stub 来模拟函数test2 的功能。

我编写了一个测试用例,其中test2 返回true,因此test 函数成功完成执行。但是我想要一个测试用例,第一个实例test2 返回false,它等待延迟,下一次test2 返回true。为此,我编写了如下测试用例:

var clock;
var result;
var test2stub;
var count = 0;

before(function () {
    clock = sinon.useFakeTimers();
    //object is defined before
    test2stub = sinon.stub(object,"test2", function () {
        console.log("Count is: " + count);
        if (count === 0) {
            return (Bluebird.resolve(false));
        }
        else if (count === 1) {
            return (Bluebird.resolve(true));
        }
    });
    clock.tick(1000);    
    object.test("xyz")
    .then(function (data) {
        result = data;
    });
    clock.tick(1000);
    count = count + 1;
    clock.tick(1000);
});

after(function () {
    test2stub.restore();
    clock.restore();
});

it("result should be undefined. Check if test2 returned false first & true next", 
  function () {
    expect(result).to.be.undefined;
});

在日志中显示 count 的值仅为 0。

【问题讨论】:

    标签: javascript node.js unit-testing sinon


    【解决方案1】:
    1. test 的代码实际上是不正确的。它从不返回成功数据。它返回undefined。该函数应该在成功时返回数据,否则您将无法将其用作下一个 .then 处理程序的参数

      .then(function (data) {
          if (data) {
              return data;
          }
      
    2. 接下来,您对函数 test 做出错误假设。它永远不会返回undefined。该函数相当危险,并且会在无尽的承诺链中永远调用自己,直到它从test2 中挤出任何非空数据。

    3. 不应在beforebeforeEach 部分启动测试代码。 beforeafter 旨在准备环境,例如伪造计时器然后恢复它们。
      it 处理程序中调用测试代码的原因之一是应以不同方式处理承诺。处理程序应该接受一个参数,该参数指示测试将是异步的,并且测试引擎给它一个超时(通常是 10 秒)来完成。该测试预计将调用done() 以指示测试成功,或者如果测试失败并且存在error 对象(或expect 抛出异常),则调用done(error)
      此外,您应该在异步操作开始之后 移动假计时器。在您的代码中,实际上第一个 clock.tick 是无用的。

    4. 使用fakeTimers 有一个技巧。您可以手动移动时间,但它不会自行移动。对于第一个刻度,它运行良好。承诺被执行。然而,在返回.delay(1000) 承诺后,将没有命令将时间向前移动。因此,要正确完成测试(而不是修改测试代码),您还必须存根 Bluebird.delay

    我会更改存根实现并执行类似的操作

    describe("test2", function(){
        beforeEach(function(){
            clock = sinon.useFakeTimers();
            test2stub = sinon.stub(object,"test2", function () {
                console.log("Count is: " + count);
                return (Bluebird.resolve((count++) > 0));
            });
            var _delay = Bluebird.delay.bind(Bluebird);
            bluebirdDelayStub = sinon.stub(Bluebird,"delay", function (delay) {
                var promise = _delay(delay);
                clock.tick(1000);
                return promise;
            });
        })
        it("should eventually return true", function (done) {
            object.test("xyz")
            .then(function (data) {
                expect(data).to.be.true;
                expect(count).to.equal(2);
                done();
            })
            .catch(function(err){
                done(err);
            });
            clock.tick(1000);
        });
    
        after(function () {
            test2stub.restore();
            clock.restore();        
            bluebirdDelayStub.restore();
        });    
    })
    

    PS 我在 Node.js 0.10.35 和 Bluebird 2.9.34 下验证了这段代码

    【讨论】:

    • @Kinil 非常感谢!你写 clock.tick(2000) 而不是 1000 有什么原因吗?
    • 检查更新的答案。我预计它会触发两个承诺(每 1000 毫秒后),但怀疑是一个陷阱,我在 Node.js 中验证了代码
    • 你为什么还要存根 Bluebird 的延迟函数?时钟滴答不会自动影响延迟功能的时间吗?
    • 好吧,也许我在 4 中解释得不是很清楚。使用fakeTimers 时,每次都应手动移动时间以触发超时。因此,在调用Bluebird.delay 之后,必须有执行clock.tick 的代码。否则会出现您注意到的情况:只有一个带有count = 0 的登录控制台,因为.delay 永远不会解析并且.then 永远不会再次调用test。还有另一种选择。不要使用fakeTimers,但这是一种不好的方法,因为您将进行一个需要 2000 毫秒的单元测试。总会有警告和测试将花费太长时间
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-28
    • 2016-07-11
    • 1970-01-01
    • 2021-11-20
    相关资源
    最近更新 更多