meteor-velocity / velocity-helpers

Testing helpers for Meteor apps to make common testing tasks straight forward.
MIT License
7 stars 0 forks source link

Syntax sugar for isolating callbacks #5

Open samhatoum opened 9 years ago

samhatoum commented 9 years ago

My current use case is a general Jasmine helper need, but I think it also applies to Meteor methods.

Consider this actualizing code:

Stripe.customers.create({
  source: token.id,
  plan: Meteor.settings.private.stripe.planId,
  email: token.email
}, Meteor.bindEnvironment(function (err, customer) {
  // the callback I'm interested in testing
}));

Here's what I have to do to get the callback that I'm interested in executing in the test code:

spyOn(Stripe.customers, 'create').and.callThrough();
Letterpress.Services.BuyService.subscribe({id: 'notNull'});
var args = Stripe.customers.create.calls.mostRecent().args;
var callback = args[args.length - 1];

I would a helper that I give methodWithCallback and ParamsToPassMethodWithCallback and it returns the callback for me. In the above example:

var callback = getMethodCallback(Stripe.customers.create, {id: 'notNull'});
ghost commented 9 years ago

Alternative to call an async callback immediately:

spyOn(Stripe.customers, 'create').and.callFake(function (data, callback) {
  // Call callback immediately
  var fakeError = {};
  var fakeCustomer = {};
  callback(fakeError, fakeCustomer);
});

Letterpress.Services.BuyService.subscribe({id: 'notNull'});

Do you really need to callback in isolation? By pulling out logic into services I usually don't need to.

samhatoum commented 9 years ago

The use-case you've presented is for immediate callback. The reason I need isolation is that I want to do is actually test the execution paths within the callback. So here's the full test code for context:

it('sends an email on successful subscription', function () {

  // - - SETUP
  spyOn(Stripe.customers, 'create').and.callThrough();
  Letterpress.Services.BuyService.subscribe({id: ''});
  var args = Stripe.customers.create.calls.mostRecent().args;
  var callback = args[args.length - 1];
  spyOn(Letterpress.Services.EmailService, 'sendConfirmation');

  // - - EXECUTE
  callback(null, {email: 'me@example.com'});

  // - - VERIFY
  expect(Letterpress.Services.EmailService.sendConfirmation).toHaveBeenCalledWith('me@example.com', 'subscribe');

});

This logic is already in a service. Here's the full code for context:

Letterpress.Services.BuyService.subscribe = function (token) {

  Stripe.customers.create({
    source: token.id,
    plan: Meteor.settings.private.stripe.planId,
    email: token.email
  }, Meteor.bindEnvironment(function (err, customer) {

    Letterpress.Services.EmailService.sendConfirmation(customer.email, 'subscribe');

  }));

};

The callback is more of a handler than the service call. I could also make a structural change and create a handlers object in the BuyService, but it seems a little overkill especially when the handler has a one-to-one relationship with its caller.

ghost commented 9 years ago

I think with this test you actually test if Stripe.customers.create correctly calls its callback. If Stripe.customers.create is 3rd party code, I wouldn't test that. (Just my opinion)

samhatoum commented 9 years ago

I'm testing the execution paths within the callback, which is stuff I write not that Stripe is calling the callback. What happens inside the callback (stripe response handler) should be tested