7

I am wondering is there is a possibility in sinon.js to stub a method only once?

For example:

sinon.stub(module, 'randomFunction', 'returnValue/function');

In my test this module.randomFunction will be called multiple times in the same test, but I only want the stub to trigger once and then restore it so the function goes back to its normal behaviour.

Simulation of the real code:

myModule.putItem(item, function (err, data) {
  if (err) {
    // do stuff
    return callback();
  } else {
    // do other stuff
    return callback(null, data);
  }
});

The first time I want to trigger the error, the other times I just want it to continue the real flow.

Is this possible in sinon?

Kind regards,

Jimmy

Edit: I posted a solution I found for my problem based on the answer of @Grimurd

jruts
  • 396
  • 4
  • 11

4 Answers4

7

Yes it is possible. Assuming you are using mocha as your testing framework.

describe('some tests', function() {    
    afterEach(function() {
        sinon.restore();
    })

    it('is a test with a stub', function() {
        // This gets restored after each test.
        sinon.stub(module, 'randomFunction', 'returnValue/function');
    })
})

Check out the sinon sandbox api for more info.

UPDATE

To answer your actual problem,

describe('some tests', function() {
    afterEach(function() {
        sinon.restore();
    })

    it('is a test with a stub', function() {
        // This gets restored after each test.
        sinon.stub(module, 'randomFunction')
            .onFirstCall().returns('foo')
            .onSecondCall().returns('bar')
            .onThirdCall().returns('foobar');
    })
})

Documented on http://sinonjs.org/docs/ search for stub.onCall(n)

Update 2: Since v5 sinon now creates a sandbox by default so it's no longer necessary to explicitly create a sandbox. See the migration guide from v4 to v5 for more information

grimurd
  • 2,750
  • 1
  • 24
  • 39
  • I probably did not phrase it correctly, but I meant that the stub gets called multiple times in the same test. – jruts May 20 '16 at 09:55
4

Solution:

sandbox.stub(module, 'putItem')
  .onFirstCall().yields('ERROR')
  .onSecondCall().yields(null, item)

Based on the answer of @grimurd I managed to get it working with 'yields'. Yields triggers the first callback function it finds in the original method signature.

So on the first call I basically say callback('error') and on the second call I say callback(null, item).

Somehow I do wonder if callback would have been a better method name than yields ;)

Thanks for the answers!

jruts
  • 396
  • 4
  • 11
4

The other answers seem to ignore a key part of the question: "I only want the stub to trigger once and then restore it so the function goes back to its normal behaviour".

The following stubs the given method once, and then reverts back to the previous behavior:

let stub = sandbox.stub(module, 'method')
    .onFirstCall().returns(1)
    .onSecondCall().callsFake((...args) => {
        stub.restore();
        return module.method(...args);
    });
Joey Kilpatrick
  • 1,394
  • 8
  • 20
  • 1
    Thank you, this is the most helpful answer actually. Though I wonder, why there's no simpler way of achieving it. For example, in Jest it behaves like this by default, without the need to restore a stub after the first call. – mvlabat Oct 29 '20 at 15:52
3

IMO there is a simpler way to achieve the expected behavior. From the docs:

stub.callThrough();
Causes the original method wrapped into the stub to be called when none of the conditional stubs are matched.

So, a real example would be:

let stub = sandbox.stub(module, 'method')
  .onSecondCall().returns('whatever');

stub.callThrough();

Notice that the original method will also be executed by the first time it gets called.