Sinon: Chaining sinon.stub() with multiple .withArgs only stubs the last declaration

Created on 25 Sep 2012  ·  23Comments  ·  Source: sinonjs/sinon

Chaining sinon.stub() with multiple .withArgs only stubs the last declaration

var stubbedFunction = sinon.stub().withArgs(argument1).returns(value1).withArgs(argument2).returns(value2);

then only the last declaration (.withArgs(argument2).returns(value2)) is actually stubbed

Most helpful comment

This is working properly with Sinon 1.7.3:

var s = sinon.stub();
s.withArgs(1).returns('a');
s.withArgs(2).returns('b');
s(1); // -> 'a'
s(2); // -> 'b'
s(3) // -> undefined

All 23 comments

Need to make withArgs modify and return the original stub it was created from, when available.

+1 For this. On a related note, I would like withArgs to optionally allow pass through to the original method for non-matched arguments.

For example, if I want to stub the Node.js fs.readFileSync() method, I want Sinon to retain the original implementation so that require() (which uses readFileSync) is not broken, but use the stubbed method for a particular file for my tests.

Example for the previous comment:

beforeEach(function() {
  global.fs = require('fs');
  sinon.stub(fs, 'readFileSync').onlyWithArgs('my-file.txt').returns('Contents of file');

  // Then require the module under test, which uses fs.readFileSync() internally
  // require() uses original method with correct calling context
  global.myModule = require('../src/my-module');
});

afterEach(function() {
  fs.readFileSync.restore();
});

it('does something with the file', function() {
  expect(myModule.loadFromFile()).toEqual('Contents of file');
});

I would like to suggest that @froots above post is posted somewhere in the docs. I searched for a long time before I found this solution on how to mock files that includes other dependencies through require

Good idea. Want to take a stab at it? http://github.com/cjohansen/sinon-web/

This is working properly with Sinon 1.7.3:

var s = sinon.stub();
s.withArgs(1).returns('a');
s.withArgs(2).returns('b');
s(1); // -> 'a'
s(2); // -> 'b'
s(3) // -> undefined

I don't think that's an example of it working, since it still doesn't chain (you have to use the original stub/mock).

True. But it's good enough and it makes clear what's going on. Chaining leaves room for interpretation.

I agree with @mantoni. The purpose of the stub is to indicate the contractual response of the service/query. This style expresses that better.

I think the docs could probably be improved here.

I was expecting the following to work:

var someObj = {
    someProp: sinon.stub().withArgs("foo").returns(true)
}

It obviously didn't work as intended. But, more importantly, it didn't fail correctly. Instead of someObj.someProp failing to return true when foo isn't provided, it always returned true. Since the returns clause returns a Behavior instance, someObj.someProp dutifully returns true regardless of input.

I understand the rationale behind the chaining structure returning different types of objects. But users are misled as to what those returned objects are. Since the methods that CallObjects and Behaviors expose match the API provided by Stubs, it's easy to see why one would assume they _were_ stubs.

I agree with @jasonkarns. I just fell into the same "trap": inlining the stub declaration does not return the expected stub instance. However, chaining appears to work as expected when using mocks:

var someObj = {
    someProp: sinon.mock().withArgs("foo").returns(true)
}

Is there any reason for this?

Not sure if I'm missing something, but is @froots point possible now? Had to do this:

sinon.stub(fs, 'readdirSync', (dir) => {
    if (dir === 'foo-path') {
        return [
            'my.js',
            'fake.js',
            'stuff.js'
        ];
    }

    return fsReaddir(dir);
});

Wanted to do something like:

var myStub = sinon.stub(fs, 'readdirSync', fs.readdirSync);

myStub
    .withArgs('foo-path')
    .returns([
        'my.js',
        'fake.js',
        'stuff.js'
    ]);

I think this is fixed now, the example below works as intended with [email protected]

var Dummy = {
    doSomething: function(something) {
        return 'doing ...' + something;
    }
}

sinon.stub(Dummy, 'doSomething')
    .withArgs('sleep').returns('sleepy')
    .withArgs('eat').returns('eating');

console.log(Dummy.doSomething('sleep'));
console.log(Dummy.doSomething('eat'));

@valentin-radulescu-hs it still fails on my side with 1.17.7.
It still takes the latest returns as the only return value, regardless of the withArgs (as pointed by @jasonkarns https://github.com/sinonjs/sinon/issues/176#issuecomment-78191790).

What does work for me is @mantoni 's answer https://github.com/sinonjs/sinon/issues/176#issuecomment-33636496, by not chaining withArgs().

var s = sinon.stub();
s.withArgs(1).returns('a');
s.withArgs(2).returns('b');

@zurfyx are you running on node or in browser? Which version of node/browser are you using? Not sure if it matters but I'd like to try and replicate on my end.

Hey @valentin-radulescu-hs. I'm running it on Node, using babel-cli 6.22.2

  • Node 6.9.4
  • NPM 3.10.10

sorry for the late reply

@zurfyx I've tried replicating with the config you mentioned using the same code as I wrote above and it outputs to the console sleepy and eating as expected. Not sure why it's causing you issues 😢

@zurfyx Just to be _totally_ sure, have you tried this?

grep version node_modules/sinon/package.json
rm -r node_modules
rm npm-shrinkwrap
npm install
grep version node_modules/sinon/package.json

Most of the times I am having problems it is due to the package version I am expecting not being the same as what is installed.

@fatso83 It doesn't work either. grep output version is still 1.1.7.7.

This is my full test, in case it can help:

  it('test', () => {
    const s = sinon.stub();
    s.withArgs('a').returns('1');
    s.withArgs('b').returns('2');

    console.info(s('c'));
  });

s('a'): '1'
s('b'): '2'
s('c'): undefined

  it('test', () => {
    const s = sinon.stub()
      .withArgs('a').returns('1')
      .withArgs('b').returns('2');

    console.info(s('c'));
  });

s('a'): '2'
s('b'): '2'
s('c'): '2'

I am able to reproduce this behavior by using sinon.createStubInstance as well.

const stub = sandbox.stub();
      const myObj = function(){};
      const objOne = sinon.createStubInstance(myObj);
      const objTwo = sinon.createStubInstance(myObj);
      stub.withArgs(objOne).returns('1');
      stub.withArgs(objTwo).returns('2');

      console.info(stub(objOne));
      console.info(stub(objTwo));

Output:
2 2

I thought sinon.createStubInstance was supposed to create a new stub object. Is that incorrect?

I'm not completely sure if this question belongs to this thread, but it definitely has something to do with it. In a project with sinon 1.17.7 I used to chain several withArgs statements, where the last one was a "grab-all" matcher that acted as a default. For example:

const s = sinon.stub();
s.withArgs(1).returns(1)
 .withArgs(2).returns(2)
 .withArgs(sinon.match.any).returns('my-default-value')

s(1) // 1
s(5) // my-default-value

Now I'm using 4.1.3 and the behavior changed, it always returns my-default-value. Was this intended? If so, is there a way to define a default/fallback value in a chain of withArgs?

I don't think a change was ever intended. If you can provide a fix for the
regression feel free! I can help track down where it happened using got
blame.

Den man. 15. jan. 2018, 11.13 skrev David García notifications@github.com:

I'm not completely sure if this question belongs to this thread, but it
definitely has something to do with it. In a project with sinon 1.17.7 I
used to chain several withArgs statements, where the last one was a
"grab-all" matcher that acted as a default. For example:

const s = sinon.stub();s.withArgs(1).returns(1)
.withArgs(2).returns(2)
.withArgs(sinon.match.any).returns('my-default-value')
s(1) // 1s(5) // my-default-value

Now I'm using 4.1.3 and the behavior changed, it always returns
my-default-value. Was this intended? If so, is there a way to define a
default/fallback value in a chain of withArgs?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/sinonjs/sinon/issues/176#issuecomment-357638653, or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAluXMzqALJ0JlAM3hUaiK1SSyca9H74ks5tKyS4gaJpZM4AK2eu
.

>

[image: --]

Carl-Erik Kopseng
[image: https://]about.me/kopseng
https://about.me/kopseng?promo=email_sig&utm_source=email_sig&utm_medium=email_sig&utm_campaign=external_links

@fatso83 I've narrowed it down, and apparently it was on purpose. To me, it feels more natural specifying a fallback in the last withArgs statement, although in this case just switching the order and putting .withArgs(sinon.match.any) in the first position solves the issue

Was this page helpful?
0 / 5 - 0 ratings

Related issues

fearphage picture fearphage  ·  3Comments

NathanHazout picture NathanHazout  ·  3Comments

kbirger picture kbirger  ·  3Comments

sudhirbits picture sudhirbits  ·  4Comments

kevinburkeshyp picture kevinburkeshyp  ·  4Comments