Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stubbing a function in sinon to return different value every time it is called

I have a function as shown below:

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));
    };

I am trying to write unit tests for this function. I am using sinon.stub to mock the functionality of the function test2.

I wrote a test case where test2 returns true and therefore the test function successfully completes execution. However I want a test case where on the first instance test2 returns false, it waits for delay and next time test2 returns true. For that I wrote my test case as below:

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;
});

In the logs it shows that count has value 0 only.

like image 302
user1692342 Avatar asked Oct 19 '22 06:10

user1692342


1 Answers

  1. The code of test is actually incorrect. It never returns data on success. It returns undefined. The function should return data on success otherwise you won't be able to use it as parameter for next .then handler

    .then(function (data) {
        if (data) {
            return data;
        }
    
  2. Next you make wrong assumptions about function test. It will NEVER return undefined. The function is rather dangerous and will call itself forever in an endless chain of promises until it squeezes out any not-null data from test2.

  3. One shouldn't launch test code in before or beforeEach section. before and after are meant to prepare the environment like faking timers and then restoring them.
    One reason for calling tested code in the it handler is because promises should be handled differently. The handler should accept a parameter which indicates that the test will be asynchronous and the the test engine gives it a timeout (usually 10 secs) to complete. The test is expected to either call done() to indicate test is successful or call done(error) if it failed and there is an error object (or expect threw an exception).
    Also you should move the fake timer after the async operation started. In your code actually the first clock.tick is useless.

  4. There is a trick with using fakeTimers. You can move time manually however it doesn't move on its own. For the first tick it works well. The promise is executed. However upon returning .delay(1000) promise, there will be no command to move the time forward. So, to finish the test correctly (not to modify tested code) you have also to stub Bluebird.delay

I would change the stubs implementation and do something like this

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 I verified this code under Node.js 0.10.35 and Bluebird 2.9.34

like image 105
Kirill Slatin Avatar answered Oct 22 '22 01:10

Kirill Slatin