Search code examples
javascriptunit-testingrequirejssinonproxyquire

Stubbing request across entire project


I'm writing tests for a small REST library that implements OAuth's refresh grant on top of the request library. As part of it's functionality, it's providing a retry function that has something like this in rest.js:

const auth = require('./auth');
const request = require('request');

function retry(headers, responseHandler) {
  headers.auth.bearer = auth.getToken();
  request(headers, function(err, resp) {
    if (resp.error && resp.error == 'invalid_token') {
      return auth.renew(function(e, r) { retry(headers, responseHandler); });
    }
    // handle happy case
  });
});

What auth.renew does is posting to the token provider with the information in headers and internally known refresh token. The important bit is that it does this by using request.post, passing the recursive event handler down to the request library.

Naturally, when I test this, I don't want outgoing HTTP calls. So I use sinon to stub them out:

const requestStub = sinon.stub(),
    rest = proxyquire('./rest', { 'request': requestStub }),
    authFail = { error: 'invalid_token' },
    authSuccess = { token: '123' };

describe('#retry', () => {
  it('calls retry twice on failed auth', () => {
    requestStub.yields(null, authFail);
    requestStub.post = sinon.stub().yields(null, authSuccess);

    retry({}, () => {});
    sinon.assert.calledTwice(requestStub);
  });
});

The problem is that auth.renew happily goes on to require('request') on it's own and thus never seeing my stub. So I guess my question is: How do I make auth use my stubbed request instead of its own?

I know sinon can stub XHR, but that seems like a lot of low-level effort for what I want.


Solution

  • So the solution I came up with was to replace the entire auth.renew method, so the thing looks like this:

    const requestStub = sinon.stub(),
        rest = proxyquire('./rest', { 'request': requestStub, 
          './auth': { 
            renew: function(callback) {
              requestStub.yields(null, authSuccess);
              callback();
            }
        }),
        authFail = { error: 'invalid_token' },
        authSuccess = { token: '123' };
    
    describe('#retry', () => {
      it('calls retry twice on failed auth', () => {
        requestStub.yields(null, authFail);
    
        retry({}, () => {});
        sinon.assert.calledTwice(requestStub);
      });
    });