[rspec-users] Stopping example execution?

David Chelimsky dchelimsky at gmail.com
Sun Jun 29 07:09:47 EDT 2008


On Jun 28, 2008, at 7:27 PM, Britt Mileshosky wrote:

> ----------------------------------------
>> Date: Sat, 28 Jun 2008 18:24:17 -0500
>> From: philodespotos at gmail.com
>> To: rspec-users at rubyforge.org
>> Subject: Re: [rspec-users] Stopping example execution?
>>
>> On Sat, Jun 28, 2008 at 5:57 PM, Britt Mileshosky
>> wrote:
>>>
>>> Hello, I'm wondering If I am missing something here when creating  
>>> an example that sets an expecation at the top or beginning of an  
>>> action but requires you to stub / mock everything that follows.
>>>
>>> Example:
>>> I want to test that a certain controller is running a  
>>> before_filter...thats easy:
>>>
>>> - controller.should_receive(:require_user)
>>> - do_get
>>>
>>> But now i've got to mock / stub everything else that comes behind  
>>> this filter so that I don't receive 'unexpected method' errors, or  
>>> other blowups because I am requesting the whole action.  Is there  
>>> anyway to stop execution after an expectation has been met? It  
>>> seems to me that this might clean things up a bit.  Not sure, I'm  
>>> still fairly new to BDD/Mocking by about 2 weeks.
>>
>> Yep, you can stub out the requested action on the controller. Say
>> you're testing that the :index action requires authentication:
>>
>>  controller.should_not_receive(:index)
>>  stub_not_logged_in
>>  do_get
>>
>> Or the opposite:
>>
>>  controller.should_receive(:index)
>>  stub_logged_in
>>  do_get
>>
>> Personally I prefer expecting the action instead of expecting the
>> filters, but I think both would accomplish the same goal, as long as
>> you tested the filter on its own. If you just wanted to stub out the
>> action to prevent it from doing anything, you could of course just  
>> use
>> controller.stub!(:index).
>>
>> HTH
>>
>> k
>> _______________________________________________
>> rspec-users mailing list
>> rspec-users at rubyforge.org
>> http://rubyforge.org/mailman/listinfo/rspec-users
>
>
> So i did something like.
>
> - controller.should_receive(:before_filter_action)
> - controller.stub!(:action_after_filter)
> - do_get
>
> Works nicely... but if you think about it and look closely, we are  
> still stubbing methods after the intended expectation was met.  This  
> method just happens to encapsulate a whole other set of methods,  
> which is why it works nicely.
>
> But lets say i have something like this
>
> ---------------------------------------------------------------------------------------------------
>
> def some_action
>    @user = self.current_user
>    @account = self.current_account if self.has_account?
>    @person = @account.people.find(params[:person])
> end
>
> ---------------------------------------------------------------------------------------------------
>
> describe "with a logged in user"
>
>  before(:each) do
>    controller.stub!(:current_account)
>    @account = stub_model(UserAccount)  # Shouldn't have to stub here?
>    @person = stub_model(User)              # Shouldn't have to stub  
> here?
>    @people = mock("list of people")         # Shouldn't have to stub  
> here?
>    @people.stub!(find)                             # Shouldn't have  
> to stub here?
>    @account.stub!(:people).and_return(@people)           # Shouldn't  
> have to stub here?
>  end
>
>  it "should find current user" do
>    controller.should_receive(:current_user).and_return(@mockUser)
>    do_get
>  end
>
>  describe "who has an account" do
>    ... should put account stubbing here with examples
>
>
>   describe "which has people" do
>     ...  should put people stubbing here with examples
>   end
>
>   describe "which has 0 people" do
>     ...
>   end
>
>
>  end
>
>  describe "who doesnt have an account" do
>     ...
>  end
>
> end
>
> ------------------------------------------------------------------------------------------------------------------------------
>
> Notice I have to stub everything out at the first before(:each)  
> declaration because my "should find current user" example will blow  
> up because of unexpected methods after the expectation. All I want  
> to know is that the current user is expected to be found and stop.   
> My examples LATER should define stubs and expectations.
>
> I'd like something like
>
> -  
> controller 
> .should_receive(:current_user).and_return(@mockUser).end_example
>
> Am i going about it all wrong?  Is there something I'm missing or  
> does this just seem natural...


You're doing the right thing by handling the stubs before(:each) -  
that's what it's therefore - to set up the environment so things will  
run.

I would recommend avoiding instance variable assignment there,  
however. If you need a variable, create it directly in the example.  
This is something that I've only recently started doing and I find  
that it keeps things much more clear.

There are a few things you can do to tidy up the stubs a bit. You  
could organize things differently to express the relationships better:

before(:each) do
   controller.stub!(:current_user).and_return(stub_model(User))
   controller.stub!(:has_account?).and_return(true)
   controller.stub!(:current_account).and_return(
     stub_model(UserAccount,
       :people => stub('people',
         :find => stub_model(Person)
       )
     )
   )
end

If you wrap self.people.find in self.find_person in Account (which  
fixes the Law of Demeter violation), like this:

def some_action
    @user = self.current_user
    @account = self.current_account if self.has_account?
    @person = @account.find_person(params[:person])
end

... you can reduce the before to this:

before(:each) do
   controller.stub!(:current_user).and_return(stub_model(User))
   controller.stub!(:has_account?).and_return(true)
   controller.stub!(:current_account).and_return(
     stub_model(UserAccount,
       :find_person => stub_model(Person)
     )
   )
end

In cases like current_user, you don't really need to stub that because  
the controller will just return nil.

If has_account? actually checks for current_account.nil?, then you  
don't have to stub that either. Now you just have this:

before(:each) do
   controller.stub!(:current_account).and_return(
     stub_model(UserAccount,
       :find_person => stub_model(Person)
     )
   )
end

This is a *bit* more organized, but still quite busy. In the end, when  
you're dealing with code that violates Tell, Don't Ask (which is quite  
natural in Rails controllers), you have to stub a lot of stuff to get  
the isolation you want. It's a tradeoff.

HTH,
David


More information about the rspec-users mailing list