0

I want to test a route that makes external api calls. I would like to stub the functionThatShouldBeStubbed so I can skip the external api call and focus on testing the route instead.

I am using Sinon and rewire, because if I understood correctly I cannot stub a function that was exported the way it currently is.

However, it seems like even though rewire replaced the function, my test is still making external api call. It seems like sinon is not aware that the function was rewired. How can I make this situation work?



//--------------------------
//../target.js
const functionThatShouldBeStubbed = async () => {
    const results = await external_API_call();
    return results;
}

module.exports = { 
 functionThatShouldBeStubbed, 
 /*more other functions*/
}

//--------------------------
//../index.js
app.use(require('endpoint.js'));

//--------------------------
//endpoint.js
const { functionThatShouldBeStubbed } = require("target.js");
router.post('endpoint', async(req, res) => {
    //do lots of stuff

    const results = await functionThatShouldBeStubbed();
    if(results.error) { return res.status(207).send({ /*stuff */})}
    //...more stuff
})


//--------------------------
//test.js
const server = require("../index.js");
const rewire = require('rewire')
const restoreTarget = rewire('../target.js');


describe("Should return appropriate error code to requester", function () {
    it("Should return 207 in this case", function (done) {
        const targetStub = sinon.stub().resolves({msg: 'fake results', statusCode: 207})
        const targetRewired = restoreTarget.__set__("functionThatShouldBeStubbed", targetStub);

        chai.request(server)
            .post("/endpoint")
            .send('stuff over')
            .catch((error) => {
                console.log("Error: ", error)
                done();
            })
            .then((res) => {
                expect(targetStub.callCount).to.equal(1);
                res.should.have.status(207);

                restoreTarget();
                targetStub.restore();
                done();
            })
    })
})

Many thanks! Edit: updated code for more detail Edit2: updated code again to show import method

1 Answers1

0

You shouldn't need rewire at all here based on how your module is being exported. The following should work

//test.js
const target = require ("../target");
const server = require("../index");

describe("Should return appropriate error code to requester", () => {
  it("Should return 207 in this case", done => {
    const targetStub = sinon
      .stub(target, "functionThatShouldBeStubbed")
      .resolves({msg: 'fake results', statusCode: 207})
    chai.request(server)
      .post("/endpoint")
      .send('stuff over')
      .then(res => {
         expect(targetStub.callCount).to.equal(1);
         res.should.have.status(207);
         targetStub.restore();
         done();
      })
  })
})
James
  • 80,725
  • 18
  • 167
  • 237
  • I tried your code but it's still calling the external api. I can see that a stub is created, but when the code executes it's still calling the original function instead of a stub. It almost seems like the function that I stubbed is not the same one that was executed in the router. Would you have any suggestions that I can look into? Thank you. Edit: I updated the original code, hope that would provide more information. Thank you very much again. – fishcakesAndCoffee Jan 26 '20 at 04:22
  • 1
    @fishcakesAndCoffee do you happen to destructure the function when you import? i.e. `const { func } = ...`, if so, you'll need to import the whole module and then pluck out that individual function to use. – James Jan 26 '20 at 08:20
  • The module is imported in full ```const target = require('../target.js')```, so I don't think that's the part. Another thing is I have no problem stubbing third party modules (if that's the right term), ie packages that I installed via npm. I have been able to stub things like ```request-promisify``'s POST call, etc. It's mainly the functions that I wrote myself, that I am having trouble to stub. – fishcakesAndCoffee Jan 26 '20 at 18:07
  • 1
    @fishcakesAndCoffee but is it imported in full in your code and your test though? Your example code would suggests it isn't? I've [answered a question](https://stackoverflow.com/questions/47796229/sinon-not-stubbing-on-module-exports/47796465#47796465) on this topic before, and although the scenario is different, it's possible that the same problem is arising. Although, as I say, that should only become a problem if you are tree shaking and not including the full exported object. – James Jan 27 '20 at 01:24
  • I really apologize on making you revisit this question again and again, but I am really quite lost on what I have done wrong. The ```target.js``` exports a destructured object such as ``` modules.export = { func1, func2, func3...}```. That file is then imported into the test file, as ```const target = require('../target.js')```, which to my understanding, is importing it in full. Am I misunderstanding anything here? Thank you very much for taking time on answering all the questions. – fishcakesAndCoffee Jan 27 '20 at 04:02
  • I also took a look at the linked question you supplied. Indeed it is a very similar situation, as the ```endpoint.js``` is importing ```target.js``` to use the function. It seems highly likely that the function that is used in endpoint.js is not the same as the one I imported into the test file... in that case, without altering the source code, would there be a way to resolve with proxyquire? – fishcakesAndCoffee Jan 27 '20 at 04:05
  • @fishcakesAndCoffee you dont need proxyquire, I think you are missing my point, can you show how you import `target.js` in your *source*, not your test – James Jan 27 '20 at 08:45
  • I edited the initial code... and as I changed it I think I get what you mean, did you mean I had to import the entire object into the source code? ```const target = require('target.js')``` and call ```functionThatNeedsToBeStubbed()``` as ```target.functionThatNeedsToBeStubbed()```? In this case I would need an alternative solution as it involve changing the source code, which I don't have full control over. – fishcakesAndCoffee Jan 27 '20 at 15:35