Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Possible to stub with sinon.js? #55

Closed
florianschmidt1994 opened this issue Jul 27, 2015 · 15 comments
Closed

Possible to stub with sinon.js? #55

florianschmidt1994 opened this issue Jul 27, 2015 · 15 comments

Comments

@florianschmidt1994
Copy link

Hello,

I'm trying to stub request-promise using sinon, however could not find a way yet on how to do that.
Back when i was using request itself, it looked something like this:

before(function (done) {
        sinon.stub(request, 'get').yields(null, { statusCode: 200 }, fs.readFileSync(__dirname + '/data/' + filename));
        done();
    });

    after(function (done) {
        request.get.restore();
        done();
    });
}

Any suggestions on how to do this with promise-request?
Feel free to close this issue if it doesn't fit in here.

@analog-nico
Copy link
Member

Hi @florianschmidt1994 , I assume you need a way to create Sinon stubs that return a promise. There are a few Sinon add-ons out there you might try out. I didn't use them myself though.

When you don't want to use an add-on but Sinon in its original form you may "return a promise":

var request = require('request-promise');
var BPromise = require('bluebird');

sinon.stub(request, 'get').returns(BPromise.resolve(fs.readFileSync(__dirname + '/data/' + filename)));

request.get()
    .then(function (responseFromFile) {
        // ...
    });

(Found this neat trick here.)

I hope this helps. It would be great if you let me know what you end up using.

@florianschmidt1994
Copy link
Author

I'll look into into these two packages and post an update when the problem is solved. Thanks!

@MarkHerhold
Copy link

I solved this issue by mocking the request-promise function and using Bluebird to create the fake response promise.

var Bluebird = require('bluebird');
var sinon = require('sinon');
// ...
it('should mock up a request-promise POST call', function() {
    var rpStub = sinon.stub(rp, 'post'); // mock rp.post() calls

    // calls to rp.post() return a promise that will resolve to an object
    rpStub.returns(Bluebird.resolve({
        fakedResponse: 'will return exactly this'
    }));
});

I am unable to figure out how to mock the entire request-promise module, however. :(
It would be awesome if someone could figure it out - I think the only way to make it happen would be to stub out the actual required dependency, but I would rather not do that.

@florianschmidt1994
Copy link
Author

We found a solution for our problem by mocking the whole request-promise module using mockery

before(function (done) {

    var filename = "fileForResponse"
    mockery.enable({
        warnOnReplace: false,
        warnOnUnregistered: false,
        useCleanCache: true
    });

    mockery.registerMock('request-promise', function () {
        var response = fs.readFileSync(__dirname + '/data/' + filename, 'utf8');
        return bluebird.resolve(response.trim());
    });

    done();
});

after(function (done) {
    mockery.disable();
    mockery.deregisterAll();
    done();
});

describe('custom test case', function() {
    //  Test some function/module/... which uses request-promise 
    //  and it will always receive the predefined "fileForResponse" as a data, e.g.
    //  var request = require('request-promise')
    //  request().then(function(data){
    //  ➞ data is what is in fileForResponse
    //  })
});

As far is i am concerned, this issue can be closed now.

@analog-nico
Copy link
Member

I just added a summary of our discussion as a new section in the README. Thanks for your contributions @MarkHerhold and @florianschmidt1994 !

@Typhlosaurus
Copy link

Typhlosaurus commented Nov 1, 2016

For future reference, if you want to stick to sinon rather than switching to mockery, can use: sinon.stub(rp, 'get') and change your own modules usage from rp({...}) to rp.get({..}

Also applies to rp.post etc.

@oshalygin
Copy link

Definitely prefer the @MarkHerhold and @Typhlosaurus solution. Bringing in mockery just to stub out a few responses feels like massive overkill.

@neoadventist
Copy link

@MarkHerhold @oshalygin @analog-nico could we have a section on how to do this with proxyquire? Example code would be nice! This really is a pain for me.

@paulckim
Copy link

paulckim commented Nov 22, 2017

I may be late to the party but it is also worth pointing out that you do not need to change your modules as @Typhlosaurus helpfully pointed out.

You can just stub purely using Sinon as follows:

let getStub = sandbox.stub(rp, 'Request');
getStub.resolves(res);

That way you can just keep your http request calls in your modules as follows:

rq(options)
.then((res) => {
     .....
})

The above approach works perfectly fine for me.

I'm not sure if rq.Request has always been exposed from the rp module but I'd bargain it's always been there.

@analog-nico I looked at the R-P docs and when I first used R-P I had the misconception that the only way to mock was to use bulky dependencies. It may be worth noting this approach for people starting fresh dev projects like me that they can stub and mock properly before having to install bulk. Just a helpful suggestion.

@neokaiyuan
Copy link

neokaiyuan commented Mar 17, 2018

FYI in case anyone else has this issue, I managed to mock HTTP requests with Nock (https://github.com/node-nock/nock) instead of stubbing rp. This is not ideal because my unit tests will depend on the internal mechanics of rp, but at least I am able to unit test locally. I wasn't able to get @HystericBerry's solution (or any other solution from #55 or #61) to work - rp kept trying to send out requests despite all my stub attempts with different permutations of mockery and sinon. Glad I tried Nock before digging deeper into why Sinon isn't able to stub Request-Promise.

I would love if someone could explain why Sinon can't stub Request-Promise! The Request-Promise README (https://github.com/request/request-promise#mocking-request-promise) says we need "a library that ties into the module loader and makes sure that your mock is returned whenever the tested code is calling require('request-promise')", which apparently Sinon does not do. Why does Sinon work for stubbing other modules but not RP?

@neokaiyuan
Copy link

Also learned: In addition to stubbing HTTP requests with Nock, we can also spy on HTTP requests with the isDone() method on Nock expectations: https://github.com/node-nock/nock#isdone

@paulckim
Copy link

paulckim commented Mar 21, 2018

@kaiyuanneo when you say

my unit tests will depend on the internal mechanics of rp

Ok, but what exactly are you doing? Do you know this for sure and can you give examples? And by "internals" what exactly do you mean? It doesn't help RP to say something doesn't work without giving exact details. Are there any code snippets that help us replicate? If what you say is true, then I'm interested in how you are using RP. How are you using RP to rely on the "internals" of RP?

I would love if someone could explain why Sinon can't stub Request-Promise!

It can. Scroll down to 4.

The Request-Promise README (https://github.com/request/request-promise#mocking-request-promise) says we need "a library that ties into the module loader [...]

Not necessarily. That's a solution to a very specific problem. The lack of mock and stub documentation in RP is not an indication that it cannot be done well. Unfortunately, whoever wrote that section of the document made it sound definitive - which is out of date. As a matter of fact, that very example literally came from this thread; therefore, it is not the core RP devs themselves giving a definitive answer that RP is unstub/unmock/able. Rather, that documented code snippet should be seen as a suggestion. Scroll up to see @florianschmidt1994 who suggested that exact solution you referenced.

I probably should have given a more in depth answer in my first comment. The reason why this simplistic approach works is because the require function caches the module. This means that Sinon is actually stubbing the cached RP module in your test and that your tests are stepping through your code using that same cached module. If you don't clear your "require" cache or don't require in a local scope, then the basic, clean implementation works. (Basically, don't do anything funky).

Summarized code below:

// inside foo.js
const rp = require('request-promise-native'); // rely on *require* to cache your module

const options = { }; // setup your options
rp(options)
.then((res) => {
    // no-op
}, console.error);
// inside fooTest.js
const rp = require('request-promise-native'); // rely on *require* to cache your module
const sinon = require('sinon');

describe('...', function() {
    // setup
    it('...', function() {
        sinon.stub(rp, 'Request').resolves({}); 
        // ^ you are actually stubbing the cached Object that "require" loads.
    });
    // teardown
});

Both foo.js and fooTest.js refer to the same Object that require loads.

@vivek12345
Copy link

vivek12345 commented Jun 8, 2019

For anyone trying to make this work with jest.
Here is how you can do it:-

const requestPromise  = require('request-promise');
const mockedRequestPromiseGetCall = jest.spyOn(requestPromise, 'get');
mockedRequestPromiseGetCall.mockImplementation(async () => {
    return {
        fakedResponse: 'will return exactly this'
    }
});

Also do remember to call your api not as

requestPromise(options);

Instead use it in the following way:-

requestPromise.get(options);

The above is needed for the mock to work.

@adrianleung815
Copy link

I think @vivek12345 's solution should be added to README as well. it's really helpful.

@rbatta
Copy link

rbatta commented Nov 15, 2021

if you need to assert that the payload is correct for whatever reason and need to use proxyquire so we don't access the cached module like @paulckim mentioned above, this is what worked with sinon.

// fooFile.js
const rp = require('request-promise');

// code to instantiate 
class fooFile {
  constructor { ... };

  async bar(task) {
    await rp.post({
      // payload that needs to be tested
    });
    return;
  }
}
const sinon = require('sinon');
const proxyquire = require('proxyquire');

   it('does a thing', async () => {
      const task = { ... };
      const expectedMessage = { ... };
      const requestStub = sinon.stub().resolves({});
      const fooFile = proxyquire('fooFile', { 'request-promise': { post : requestStub }});
      const fooFile = new fooFile();

      const emptyResponse = await fooFile.bar(task);
      assert.deepEqual(emptyResponse, undefined);
      sinon.assert.calledWith(requestStub, expectedMessage);
   });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests