[rspec-users] Problem with before filters

Matt Wynne matt at mattwynne.net
Mon Nov 2 10:55:16 EST 2009


On 2 Nov 2009, at 14:19, David Chelimsky wrote:

> On Nov 2, 2009, at 3:35 AM, Amit Kulkarni wrote:
>
>> Following is the scenario.
>>
>> describe BbPostsController, "POST Create" do
>> context "Admin" do
>>   fixtures :users, :bb_posts, :user_channels, :channels,
>> :channel_features
>>     it "should save post" do
>>       login_as(:amit)
>>       # Added to pass before filters in controller
>>       controller.stub!(:validate_channel).and_return(true)
>>       controller.stub!(:is_feature_active).and_return(Blog)
>>       controller.stub!(:load_categories).and_return(1)
>>       @post = mock_model( BbPost, :body => "test_description", :title
>> => "test123", :abstract => "test_abstract", :channel_feature_id =>  
>> "v1",
>> :published => "1", :bb_post_category_id => "1" )
>>       BbPost.stub!(:new).and_return @post
>>       @post.should_receive( :save )
>>       post :create, {:bb_post => {:title => 'test123'}}
>>       response.should redirect_to( blog_bb_posts_url(:channel =>
>> @channel.brand_name) )
>>     end
>>
>> Following is the controller code:
>> class BbPostsController < ApplicationController
>> before_filter :login_required
>> before_filter :validate_channel
>> before_filter :manager_access_required
>> before_filter :is_feature_active
>> before_filter :load_categories, :only => [ :new, :edit, :create,
>> :update ]
>>
>> # BEFORE FILTER : Feature must active
>> def is_feature_active
>>   @feature = @channel.channel_features.by_name( BLOG ).active.first
>>   unless @feature
>>      flash[ :notice ] = 'Blog feature was not active.'
>>      redirect_to requests_features_path
>>   end
>> end
>>
>> # BEFORE FILTER : Load blog categories
>> def load_categories
>>   # manager blog will not have admin specific categories
>>   @categories = ( params[ :channel ].camelize == ADMIN_CHANNEL ) ?
>> BbPostCategory.visible : BbPostCategory.visible.without_admin
>> end
>>
>> def create
>>   params[ :bb_post ][ :brand_list ] = ADMIN_CHANNEL if params[
>> :bb_post ][ :brand_list ].blank?
>>   p params
>>   @bb_post = @feature.posts.new( params[ :bb_post ] )
>>   @bb_post.user = current_user
>>   # CREATE Channel Blog post
>>   respond_to do |format|
>>     if @bb_post.save
>>       flash[ :notice ] = 'Blog post was successfully created.'
>>       format.html { redirect_to( blog_bb_posts_url ) }
>>       format.xml { render :xml => @bb_post, :status => :created,
>> :location => @bb_post }
>>     else
>> load_featured_posts if @bb_post.editorial?
>>       format.html { render :action => "new" }
>>       format.xml { render :xml => @bb_post.errors, :status =>
>> :unprocessable_entity }
>>     end
>>   end
>> end
>>
>> Now if i run my specs,i get an error which says
>>
>> NoMethodError in 'BbPostsController POST Crete dmin should sve post'
>> You hve nil object when you didn't expect it!
>> The error occurred while evluting nil.posts
>> Error comes at "@bb_post = @feature.posts.new( params[ :bb_post ] )"
>
> This tells you that @feature is nil. This is because filter that  
> declares @feature and assigns it a value is being stubbed. Although  
> very common, using filters to set instance variables makes it  
> difficult to test in isolation.
>
> I recommend pushing the model concerns to the model, and use filters  
> for application flow control concerns (like authentication/ 
> authorization) in the controller. In this case, that would mean  
> eliminating the validate_channel, is_feature_active, and  
> load_categories filters and simply passing the params to the model.  
> I'm not sure of the models and relationships, but I'm imagining  
> something like:
>
> def create
>  @bb_post = BbPost.new( params[ :bb_post ].merge(:user =>  
> current_user) )
>  respond_to do |format|
>    if @bb_post.save
>      ...
>
> All of the logic in the filters and before the assignment of  
> @bb_post can be managed in the BbPost model, where it is far easier  
> to spec in my experience.
>
> If you don't want to follow that recommendation, you'll have to  
> either set up all the state you need in the database for each  
> example, or do more invasive setup like this:
>
>  controller.instance_eval { @feature = mock('feature') }

I'd just back this up to say that one of the major benefits of test- 
driven-development is the guidance you get with your design. Mocking,  
particularly, gives you really clear feedback on whether you have a  
loosely-coupled OO design or not. If you're finding your specs are  
hard to write, and your mocks are unwieldy and complex, it's always a  
good idea to take a step back and think about whether your design  
needs some work.

>
>> Just above that line i tried to print params and i get following  
>> values:
>> #<BbPost:0x44d1cd8 @nme="BbPost_1001">
>> {"ction"=>"crete", "controller"=>"bb_posts",
>> "bb_post"=>{"brnd_list"=>"BrndPotio
>> n", "title"=>"test123"}}
>>
>> Can somebody help me with this
>> Also by using controller.stub!(:validate_channel).and_return(true)  
>> can
>> somebody tell me actually,what exactly happens here.How does it  
>> handle
>> before filters.
>
> Filters are just methods that get called implicitly by the  
> controller. RSpec's mocking framework doesn't handle them in any  
> special way (nor does any test double frameowrk that I'm aware of).  
> Stubs return values. They do not set state on the object in  
> question, which is why if you stub is_feature_active, for example,  
> that needs to be coupled with code that sets the value of @feature  
> on the controller (per above).
>
> HTH,
> David
> _______________________________________________
> rspec-users mailing list
> rspec-users at rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users

cheers,
Matt

http://mattwynne.net
+447974 430184



More information about the rspec-users mailing list