简体   繁体   中英

Stubbing a promisified function with sinon and bluebird

In the file I would like to test, I have the following code:

var httpGet = Promise.promisify(require("request").get);
httpGet(endpoint, {
    auth: {bearer: req.body.access_token},
    json: true
})
    .then(...)

Now, in my tests, I want to make sure that httpGet was called once, and make sure the parameters are valid. Before being promisified, my test looked like this:

beforeEach(function () {
    request.get = sinon.stub()
        .yields(null, null, {error: "test error", error_description: "fake google error."});
});

afterEach(function () {
    expect(request.get).to.have.been.calledOnce();
    var requestArgs = request.get.args[0];
    var uri = requestArgs[0];

    expect(uri).to.equal(endpoint);
    //...
});

Unfortunately this no longer works when request.get is promisified. I tried stubbing request.getAsync instead (since bluebird appends "Async" to promisified functions), but that does not work either. Any ideas?

Promise.promisify doesn't modify the object, it simply takes a function and returns a new function, it is completely unaware that the function even belongs to "request" .

"Async" suffixed methods are added to the object when using promisify All

Promise.promisifyAll(require("request"));

request.getAsync = sinon.stub()
        .yields(null, null, {error: "test error", error_description: "fake google error."});

expect(request.getAsync).to.have.been.calledOnce();

Just for future reference I've solved this a bit differently, and I think a little cleaner. This is typescript, but basically the same thing.

fileBeingTested.ts

import * as Bluebird from 'bluebird';
import * as needsPromise from 'needs-promise';

const methodAsync = Bluebird.promisify(needsPromise.method);

export function whatever() {
    methodAsync().then(...).catch(...);
}

test.spec.ts

import * as needsPromise from 'needs-promise';
import * as sinon form 'sinon';

const methodStub = sinon.stub(needsPromise, method);
import { whatever } from './fileBeingTested';

Then you use the methodStub to manage what calls happen. You can ignore that it's being promisified and just manage it's normal behavior. for example if you need it to error.

methodStub.callsFake((arg, callback) => {
    callback({ error: 'Error' }, []);
});

The promisified version will throw the error and you'll get it in the catch.

Anyone coming across this. I have small utility func

function stubCBForPromisify(stub) {
  let cbFn = function() {
    let args = [...arguments];
    args.shift();
    return stub(...args);
  };
  return cbFn.bind(cbFn, () => ({}));
}

In test

var getStub = sinon.stub().yields(null, {error: "test error", error_description: "fake google error."})
sinon.stub(require("request"), 'get', stubCBForPromisify(getStub))
expect(getStub).to.have.been.calledOnce();

I was running into trouble testing this using tape and proxyquire . I'm not sure what pattern/framework people are using that allowed them to modify the required 'd request object directly as shown in the accepted answer. In my case, in the file I want to test I require('jsonFile') , then call bluebird.promisifyAll(jsonFile) . Under normal conditions this creates a readFileAsync method that I want to stub. However, if during testing I try to use proxyquire to pass in a stub, the call to promisifyAll overwrites my stub.

I was able to fix this by also stubbing promisifyAll to be a no-op. As shown this might be too coarse if you rely on some of the async methods to be created as-is.

core.js :

var jsonFile = require('jsonfile');
var Promise = require('bluebird');
Promise.promisifyAll(jsonFile);

exports.getFile = function(path) {
  // I want to stub this method during tests. It is
  // created by promisifyAll
  return jsonFile.readFileAsync(path);
}

core-test.js :

var proxyquire = require('proxyquire');
var tape = require('tape');
var sinon = require('sinon');
require('sinon-as-promised');

tape('stub readFileAsync', function(t) {
  var core = proxyquire('./core', {
    'jsonfile': {
      readFileAsync: sinon.stub().resolves({})
    },
    'bluebird': { promisifyAll: function() {} }
  });
  // Now core.getFile() will use my stubbed function, and it
  // won't be overwritten by promisifyAll.
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM