[rspec-users] Preferred way to predict behavior in controller spec?

Zach Dennis zach.dennis at gmail.com
Sat May 5 03:21:18 UTC 2012


On Fri, May 4, 2012 at 7:14 PM, Patrick J. Collins
<patrick at collinatorstudios.com> wrote:
> So, I have an action like this:
>
>  def destroy
>    if purchase.user == current_user && purchase.refund
>      redirect_to purchase
>    else
>      flash[:error] = t("purchases.refund.failure")
>      render :show
>    end
>  end
>
> ...  I wanted a controller test to verify the following:
>
>  1.  It's the correct user
>  2.  The refund was successful
>  3.  User is redirected
>
> ...
>
> Initially, I thought this would go something like this:
>
>  describe "#destroy" do
>    let(:purchase) { create_purchase }
>
>    it "refunds the buyer" do
>      subject.stubs(:current_user).returns purchase.user
>      purchase.stubs(:refund).returns true
>      put :destroy, { :id => purchase.id }
>      response.should be_redirect
>    end
>  end
>
> WRONG!!!!!!!!!
>
> Obviously, that is not cool because purchase.stubs(:refund) will not be the
> right object in the context of the controller......  So since my controller is actually doing the following:
>
>  def destroy
>    if purchase.user # etc...
>  end
>
>  private
>
>  def purchase
>    @purchase ||= Purchase.find(params[:id])
>  end
>
> I could do:
>
> subject.stubs(:purchase).returns(purchase)
>
> ..  However, now that kind of defeats the purpose of put :destroy, { :id => purchase.id }...

>
> So I didn't really like that-- I am no longer verifying it's the right record,
> so maybe the things I wanted to test were actually:
>
>  1.  It finds the purchase
>  2.  It's the correct user
>  3.  The refund was successful
>  4.  User is redirected
>
> ........
>
> So I ended up making my test do:
>
>  describe "#destroy" do
>    let(:purchase) { create_purchase }
>
>    def do_destroy
>      put :destroy, { :id => purchase.id }
>    end
>
>    it "refunds the buyer" do
>      subject.stubs(:current_user).returns purchase.user
>      Purchase.any_instance.stubs(:refund).returns true
>      do_destroy
>      response.should be_redirect
>    end
>      subject.stubs(:current_user).returns purchase.user
>      Purchase.any_instance.stubs(:refund).returns true
>
>      do_destroy
>      response.should redirect_to purchase.offer
>    end
>
>    it "does not refund the buyer when it fails" do
>      subject.stubs(:current_user).returns purchase.user
>      Purchase.any_instance.stubs(:refund).returns false
>
>      put :destroy, :id => purchase.id
>      response.should_not be_redirect
>    end
>
>    it "does not refund the buyer when it's the wrong user" do
>      subject.stubs(:current_user).returns create_user
>      Purchase.any_instance.expects(:refund).never
>
>      do_destroy
>      response.should_not be_redirect
>    end
>  end
>
> But then I heard the voice of an old friend in my head, saying (with a long
> trailing echo) "any_instance is terrible practice.. never use it!"
>
> So I am curious if anyone has suggestions on how this might be improved?

Instead of any_instance you could stub the interaction with
Purchase.find in your spec:

    Purchase.stubs(:find).with(purchase.id).returns purchase

HTH,

Zach



>
> Thank you.
>
> Patrick J. Collins
> http://collinatorstudios.com
> _______________________________________________
> rspec-users mailing list
> rspec-users at rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users



-- 

--
@zachdennis
http://www.continuousthinking.com
http://www.mutuallyhuman.com


More information about the rspec-users mailing list