On Wed, Apr 15, 2009 at 9:36 AM, Fernando Perez <[email protected]> wrote:
> Hi,
>
> I used to have the following method:
>
> def Paypal.process_ipn(params)
> ...
> paypal = create!(params)
> paypal.order.update_from_ipn(completed?)
> end
>
> That method obviously is not easily specable because of the double dot
> method call, and when specing it, it would hit the DB for nothing. I
> used to actually spec the associated order status to know if everything
> went on well which breaks the isolation of unit tests. Bad bad bad.
>
> -----------
>
> So I refactored it as following:
>
> def Paypal.new_from_ipn(params)
> ...
> new(params)
> end
>
> So that it doesn't hit the DB when I spec the method, and specing the
> method can now be done in isolation, as I only set some values returned
> by IPN to something more compliant with my DB storage and then in the
> controller that calls this method:
>
> paypal = Paypal.new_from_ipn(params)
> paypal.update_order if paypal.save!
>
>
> So the paypal instance still gets saved, but instead of getting saved in
> the model it occurs in the controller, no big deal. Also I added a new
> instance method to Paypal class to abid to Demeter's law:
>
> def update_order
> order.update_from_paypal_ipn!(completed?)
> end
>
> But now I am wondering how to spec this instance method, I thought of
> the following:
>
> it "should update the associated order" do
> �...@paypal = Paypal.new(:payment_status => 'Completed')
> �...@order = @paypal.order
> �[email protected](:update_from_paypal_ipn!).with(true)
>
> �[email protected]_order
> end
>
>
> Is that spec acceptable? I mean I am not sure about the following two
> lines:
>
> @order = @paypal.order
> @order.expects(:update_from_paypal_ipn!).with(true)
>
> I thought about this other solution which is slightly more complicated:
> �...@order = Order.new
> �...@paypal = Paypal.new(:payment_status => 'Completed')
> �[email protected](:order).returns(@order)
> �[email protected](:update_from_paypal_ipn!).with(true)
> �[email protected]_order
>
> So which is best?
>
> And what do you think of my refactoring and my new specs? Did I improve
> the code or is it just crap?
> --
> Posted via http://www.ruby-forum.com/.
> _______________________________________________
> rspec-users mailing list
> [email protected]
> http://rubyforge.org/mailman/listinfo/rspec-users
>
I would probably move the call to update_order into a
before/after_create on Paypal. Then I would write a spec on Paypal
that checks that the right thing happened. I would not likely use a
mock unless Order#update_from_paypal_ipn! is very complex.
describe Paypal, "from IPN" do
it "should update its order when created" do
paypal = Paypal.new_from_ipn :ipn => {:completed => true}
paypal.save!
paypal.order.should be_completed
end
end
class Paypal
after_create :update_order
def update_order
order.update_from_paypal_ipn! completed?
end
end
By doing it in a callback you get
* transaction semantics for free
* ability to use resource_controller
* simpler model API
If you only want to update the order when Paypal.new_from_ipn is
called (and not Paypal.new, for example) then just put an ivar in
Paypal and check for it in the callback.
So to recap, I would test this behavior via the Paypal examples,
because that's where the behavior originates. I may or may not mock
the call to order.update_from_paypal depending on how complex it is.
Does that make sense?
Pat
_______________________________________________
rspec-users mailing list
[email protected]
http://rubyforge.org/mailman/listinfo/rspec-users