[rspec-users] Stopping example execution?

Britt Mileshosky mileshosky at hotmail.com
Sun Jun 29 12:18:40 EDT 2008



----------------------------------------
> From: dchelimsky at gmail.com
> To: rspec-users at rubyforge.org
> Date: Sun, 29 Jun 2008 06:09:47 -0500
> Subject: Re: [rspec-users] Stopping example execution?
> 
> 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


Thank you David, that helps quite a bit, I will adapt these principles and move on.

However, do you see where something like a return statement or end example statement could be beneficial?
If you are working from the top down with your controller action execution, then you only need to test your expectation
and then bail out of your action.  No need to further test or meet requirements on anything else in that action because your
single test has been met.

- in my example for making sure I find a user, I'd like to end execution once I DID find the user, i shouldn't have to satisfy 
requirements about finding an account and a person... I'll write those expectations later in another nested describe group, as you
can see here, in a top down process

PeopleController with a logged in user
- should find user

PeopleController with a logged in user who has an account
- should find account

PeopleController with a logged in user who doesnt have an account
- shouldn't find account
- should redirect ... 

PeopleController with a logged in user who has an account the person belongs to
- should find person
- should assign person for the view

PeopleController with a logged in user who has an account the requested person does not belong to
- should not find person
- should ...

_________________________________________________________________
The i’m Talkathon starts 6/24/08.  For now, give amongst yourselves.
http://www.imtalkathon.com?source=TXT_EML_WLH_LearnMore_GiveAmongst


More information about the rspec-users mailing list