Question

I'm trying to stub the nodejs stripe api with sinon, to test the creation of a customer using a test that looks like:

var sinon = require('sinon');
var stripe = require('stripe');
var controller = require('../my-controller');

var stub = sinon.stub(stripe.customers, 'create');
stub.create.yields([null, {id: 'xyz789'}]);
//stub.create.yields(null, {id: 'xyz789'}); //same result with or without array 

controller.post(req, {}, done);

My understanding is that stub.create.yields should call the first callback, and pass it (in this case) null, followed by an object with id of xyz789 This may be where I am mistaken

Inside my 'controller' I have the following:

exports.post = function(req, res, next) {

    stripe.customers.create({
        card: req.body.stripeToken,
        plan: 'standard1month',
        email: req.body.email
    }, function(err, customer) {

        console.log('ERR = ', err)
        console.log('CUSTOMER = ', customer)

err, and customer are both undefined.

Have I done something wrong?

EDIT

I think the issue could be around here: (stripe docs)

var stripe = require('stripe')(' your stripe API key ');

So, stripe constructor takes an api key

In my test, I'm not supplying one: var stripe = require('stripe');

But in my controller, I have:

var stripe = require('stripe')('my-key-from-config');

So, as per your example, I have:

test.js:

var controller = require('./controller');
var sinon = require('sinon');
var stripe = require('stripe')('test');

var stub = sinon.stub(stripe.customers, 'create');
stub.yields(null, {id: 'xyz789'});
//stub.create.yields(null, {id: 'xyz789'}); //same result with or without array 

controller.post({}, {}, function(){});

controller.js

var stripe = require('stripe')('my-key-from-config');

var controller = {
    post: function (req, res, done) {
        stripe.customers.create({
            card: req.body,
            plan: 'standard1month',
        }, function(err, customer) {
            console.log('ERR = ', err);
            console.log('CUSTOMER = ', customer);
        });
    }
}

module.exports = controller;
Was it helpful?

Solution

When you do this:

var stripe = require('stripe')('my-key-from-config');

The stripe library creates the customer and other objects dynamically. So, when you stub it out in one file:

test.js

var stripe = require('stripe')('test');
var stub = sinon.stub(stripe.customers, 'create');

And your controller creates another stripe instance to use in another file:

controller.js

var stripe = require('stripe')('my-key-from-config');
var controller = { ... }

The stubbed version from test has no impact on the controller's version.

SO....

You either need to inject the test instance of stripe into your controller, or use a library like nock to mock things at the http level, like so:

  nock('https://api.stripe.com:443')
    .post('/v1/customers', "email=user1%40example.com&card=tok_5I6lor706YkUbj")
    .reply 200, 
      object: 'customer'  
      id: 'cus_somestripeid'

OTHER TIPS

It looks like you're trying to isolate your #post() function from #stripe.customers.create(), in the Stripe API. @lambinator is correct in pointing out that the customers object is created, dynamically, when you call

require('stripe')('my-key-from-config')

and

require('stripe')('test')

so your stub, in the test, does not apply to #stripe.customers.create(), in the controller.

You could inject the test instance of stripe into the controller, as @lambinator suggests. Injection is pretty much the best thing ever. However, if you're writing a rubber-meets-the-road type component (like a proxy), injection is inappropriate. Instead, you can use the second export provided in the Stripe module:

Stripe.js:

...

// expose constructor as a named property to enable mocking with Sinon.JS
module.exports.Stripe = Stripe;

Test:

var sinon = require('sinon');
var stripe = require('stripe')('test');
var StripeObjectStub = sinon.stub(Stripe, 'Stripe', function(){
  return stripe;
});
//NOTE! This is required AFTER we've stubbed the constructor.
var controller = require('./controller');

var stub = sinon.stub(stripe.customers, 'create');
stub.create.yields([null, {id: 'xyz789'}]);
//stub.create.yields(null, {id: 'xyz789'}); //same result with or without array 

controller.post({}, {}, function(){});

Controller:

require('stripe').Stripe('my-key-from-config');

var controller = {
post: function (req, res, done) {
    stripe.customers.create({
        card: req.body,
        plan: 'standard1month',
    }, function(err, customer) {
        console.log('ERR = ', err);
        console.log('CUSTOMER = ', customer);
    });
}

Then #stripe.customers.create(), in your controller, will invoke your test stub.

Based on @Angrysheep's answer which uses the exported Stripe constructor (the best method IMHO), here's a working code as of the time of writing this:

Controller

//I'm using dotenv to get the secret key
var stripe = require('stripe').Stripe(process.env.STRIPE_SECRET_KEY);

Test

//First, create a stripe object
var StripeLib = require("stripe");
var stripe = StripeLib.Stripe('test');

//Then, stub the stripe library to always return the same object when calling the constructor
const StripeStub = sinon.stub(StripeLib, 'Stripe').returns(stripe);

// import controller here. The stripe object created there will be the one create above
var controller = require('./controller');

 describe('a test', () => {
     let createCustomerStub;
     const stripeCustomerId = 'a1b2c3';

     before(() => {
        //Now, when we stub the object created here, we also stub the object used in the controller
        createCustomerStub = sinon.stub(stripe.customers, 'create').returns({id: stripeCustomerId});
    });

    after(() => {   
        createCustomerStub.restore();
    });
});

EDIT: based on a comment below, this approach might no longer work.

A reasonable approach to walk around this entire issue is to use Stripe as an injected dependency, i.e. to pass the Stripe object to the relevant object's constructor. This will allow injecting a stub as part of the testing suite.

It is likely something that is not part of what you have described here.

yields is an alias for callsArg, but attempts to call the first argument that is a function and provides it the arguments using Function.prototype.apply - this means @psquared is correct in saying it does not need to be an array.

However, this isn't your problem. Attempting to recreate the given code in JSFiddle, we can see that it successfully calls back the argument.

var stripe = {
    customers: {
        create: function () {}
    }
};
var controller = {
    post: function (req, res, done) {
        stripe.customers.create({
            card: req.body,
            plan: 'standard1month',
        }, function(err, customer) {
            console.log('ERR = ', err);
            console.log('CUSTOMER = ', customer);
        });
    }
}

var stub = sinon.stub(stripe.customers, 'create');
stub.yields(null, {id: 'xyz789'});
//stub.create.yields(null, {id: 'xyz789'}); //same result with or without array 

controller.post({}, {}, function(){});

This tells me you need to show more of your code, or try writing a reduced test case to attempt to find where the problem lies.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top