Mock/stub ActiveMerchant? (or other cascading/multiple inheritance situation)

I am a bit new to mocking. I am trying to stub the
ActiveMerchant::Billing::PaypalGateway#authorize method but not clear
how to
do it. This is my code and spec.

This is the pertinent code:

module Payment
def gateway
ActiveMerchant::Billing::PaypalGateway.new(

)
end

def authorize_payment(payment_info, associated_record_type,
associated_record_id)
gateway.authorize(payment_info.amount …
end

I tried this:
ActiveMerchant::Billing::PaypalGateway.should_receive(:authorize).and_return(authorize_payment_success_response)

And it does work on its own, but when I call Payment.gateway.authorize,
ActiveMerchant still goes out to the web. I perhaps am missing
understanding
something – yes, calling gateway.authorize does create a new
ActiveMerchant::Billing::PaypalGateway, but I thought by stubbing the
class
I should be ok.

Or alternatively is there a way I can do something like:
Payment.gateway.authorize.should_receive(:authorize).and_return(nil)

I am guessing this question does not have to specifically do with
ActiveMerchant but how to mock and stub through cascading levels of
classes/objects.

David

On Apr 28, 2011, at 4:37 PM, David K. wrote:

def authorize_payment(payment_info, associated_record_type,
associated_record_id)
gateway.authorize(payment_info.amount …
end

I tried this:

ActiveMerchant::Billing::PaypalGateway.should_receive(:authorize).and_return(authorize_payment_success_response)

This is setting an expectation on the PaypalGateway object (which is a
class!). But when you call PaypalGateway.new, you get back an instance -
which is where you want to set the expectation. So what you really need
to stub is something that looks more like an instance…you’d start off
with:

gateway = stub(‘gateway’)
gateway.should_receive(:authorize)

and next you can either stub PaypalGateway.new:
ActiveMerchant::Billing::PaypalGateway.stub(:new).and_return gateway

or what I’d more likely do:
Payment.stub(:gateway).and_return gateway

either one will get you there.

Pat

On Thu, Apr 28, 2011 at 8:03 PM, Pat M. [email protected] wrote:

ActiveMerchant::Billing::PaypalGateway.new(

ActiveMerchant::Billing::PaypalGateway.should_receive(:authorize).and_return(authorize_payment_success_response)

This is setting an expectation on the PaypalGateway object (which is a
class!). But when you call PaypalGateway.new, you get back an instance -
which is where you want to set the expectation. So what you really need to
stub is something that looks more like an instance…you’d start off with:

gateway = stub(‘gateway’)

gateway.should_receive(:authorize)

and next you can either stub PaypalGateway.new:
ActiveMerchant::Billing::PaypalGateway.stub(:new).and_return gateway

or what I’d more likely do:
Payment.stub(:gateway).and_return gateway

Thanks Pat, this worked great and I think helping get my head around
doing
this.

I do have one additional question… I am testing a module here, and
noticed that I had to both include the module (Payment) and in my spec
call Payment#authorize_payment to get things working with the stub.

It seems kind of strange as if I did not include the module at the top,
then
I would get ‘undefined method `authorize_payment’ for Payment:Module’
when
called in the spec, which makes sense. But what does not make sense is
that
when I do include the Module at the top, I still have to call
Payment#authorize_payment and not just authorize_payment to get the stub
to
take (the test passes in both cases, when not explicitly declaring
Payment#… the mock does not take).

So it seems that it is as if there are two versions of Payment module

one which is explicitly connected via the rspec stub, and the other
which is
the native. Once I stub Payment explicitly, I must explicitly declare it
on
my calls, otherwise it goes to the native code. Is this correct?

I thought if I just removed the line
‘Payment.stub(:gateway).and_return(gateway)’ that I would not have to
call
Payment#authorize… and instead use authorize… but again, in this
case
the mock does not take.

Anyway, I have things working (as below), but interested in why this is
so.

require ‘spec_helper’
include Payment

describe Payment do

before(:each) do
gateway = stub(‘gateway’)
Payment.stub(:gateway).and_return(gateway)
gateway.stub!(:authorize).and_return(gateway_authorize_success_response)
end

it “should authorize payment with paypal using a valid card” do
response = Payment.authorize_payment(payment_info_success, ‘Bet’,
1000)

end

I.E., why does this not hit the stub:

it “should authorize payment with paypal using a valid card” do
response = authorize_payment(payment_info_success, ‘Bet’, 1000)

end

Em 29-04-2011 13:15, David K. escreveu:

>
associated_record_id)
you really need to stub is something that looks more like an
Payment.stub(:gateway).and_return gateway

It seems kind of strange as if I did not include the module at the
explicitly declare it on my calls, otherwise it goes to the native
require ‘spec_helper’

...

end

That is what happens in Rspec behind the scenes:

module A
def test
1
end
end

include A

puts test # 1
puts A.test # 1
puts singleton_methods.include?(:test) # false
puts A.singleton_methods.include?(:test) # false

class << A
def test
2
def
end

puts test # 1
puts A.test # 2
puts singleton_methods.include?(:test) # false
puts A.singleton_methods.include?(:test) # true

If you don’t know very much about singleton classes (some people call
them anonymous classes), you can take a look at these articles:

http://www.contextualdevelopment.com/articles/2008/ruby-singleton

Hope that helps understanding why you can’t include a mocked module in
your spec (actually you can, but it won’t work as expected…)

Cheers,

Rodrigo.

On Fri, Apr 29, 2011 at 10:05 PM, Rodrigo Rosenfeld R. <
[email protected]> wrote:

do it. This is my code and spec.

def authorize_payment(payment_info, associated_record_type,
which is where you want to set the expectation. So what you really need to
Payment.stub(:gateway).and_return gateway
It seems kind of strange as if I did not include the module at the top,
my calls, otherwise it goes to the native code. Is this correct?

response = Payment.authorize_payment(payment_info_success, 'Bet', 1000)

puts test # 1
puts test # 1
Hope that helps understanding why you can’t include a mocked module in your
spec (actually you can, but it won’t work as expected…)

Thanks Rodrigo… I think I see it. In the end I have discovered that if
I
want to mock a module, rather than going directly I need to stub out
methods
in the class I am testing which call the module and this seems to work,
otherwise (and I am not sure if this is exactly the case you are
explaining), but certainly it ‘does not work as expected’.