RSpec, Controllers, Devise, CanCan and non-trivial code

317 views
Skip to first unread message

Mark E.

unread,
Sep 21, 2010, 1:03:49 AM9/21/10
to ur...@googlegroups.com
I would like some feedback from others on the "right" way to do this.

I'm using RSpec testing a controller using Devise for authentication and CanCan for permissions. I'm loading a resource that loads data scoped by an account. Like this...

def show
  @asset = current_user.account.assets.find(param[:id])
end

I got it all to work, but I didn't do it the "right" way. Or at least what I read everywhere that says I should mock and stub out everything about the user. However, in examples like this, I feel like I'm fully white-box testing because the stubs have to do too much. The controller authenticates the user, checks their abilities (via CanCan) and all this just to find a record. The user can have multiple roles assigned so it isn't just an :admin? method needed.

For simplicity, I just do it using a Factory (via factory_girl) to create the asset like this...

describe "GET show" do
  it "assigns the requested asset as @asset" do
    asset = Factory(:asset, :account => @current_user.account)
    get :show, :id => asset.id
    assigns[:asset].should == asset
  end
end

This is working for me and to me it seems clean and easy to maintain. In my search to get this working, everything I read said I should stub it. I had a hard time even figuring out how I would stub all that out.

At the RubyWebConf, the critique I heard against the factory approach was that it gets written to the DB so it is slower especially as the test suite grows. In this particular project I'm not concerned about testing speed.

Am I being stubborn in just using a factory? Is there an easy way to do it the "right" way?

Any help or tips are appreciated.

Thanks,
-Mark E.

Matt White

unread,
Sep 21, 2010, 11:12:15 AM9/21/10
to Utah Ruby Users Group
Mark,

It's not just about it being slower, it's about testing the controller
separately from your models. You don't want to have to worry about
model errors in your controller tests, so stubbing the model helps to
isolate the test. A test that tests models and controllers (and other
things) at the same time belongs in an integration test.

With RSpec, you can do something like:

describe "GET show" do
it "assigns the requested asset as @asset" do
asset = double(Asset, {:asset_attribute => value, :save =>
true}).as_null_object
Asset.stub(:new).with(:id => asset.id).and_return(asset)
get :show, :id => asset.id
assigns[:asset].should == asset
end
end

HTH,
Matt

Mark E.

unread,
Sep 21, 2010, 12:23:21 PM9/21/10
to ur...@googlegroups.com
Matt,

Thanks for sharing the point about isolating the testing to the controller. That makes sense.

Perhaps what I need is some help from any who are willing. :)

Most of my controllers have ajax searching/sorting/paging support. It follows this pattern described on the Thoughtbot site.

http://robots.thoughtbot.com/post/613420732/recipe-ajax-searching-and-filtering

My index action looks like this...

def index
  @search = current_user.account.assets.scoped(:include => :asset_type).param_search(params)
  @assets = @search.paginate(:page => page_value, :per_page => 20, :order => [sort_column + ' ' + sort_direction])
  @total = @search.count

  respond_to do |format|
    format.html do
      render(:partial => "assets_list", :layout => false) if request.xml_http_request?
      # else renders index.html.erb
    end
    format.xml  { render :xml => @assets.to_xml(:include => { :asset_type => {:only => :name}}) }
    format.json { render :json => @assets.to_json(:include => { :asset_type => {:only => :name}}) }
  end
end

I really don't know how to make this work without using factories. The thoughtbot blog entry that demonstrated this approach uses Cucumber for feature tests and factories for RSpec controller tests.

Is there a good way to test this without factories?

Thanks,
-Mark E.


--

Mark E.

unread,
Sep 24, 2010, 12:15:31 AM9/24/10
to ur...@googlegroups.com
On Tue, Sep 21, 2010 at 9:12 AM, Matt White <matt...@gmail.com> wrote:
[...]

With RSpec, you can do something like:

describe "GET show" do
 it "assigns the requested asset as @asset" do
   asset = double(Asset, {:asset_attribute => value, :save =>
true}).as_null_object
   Asset.stub(:new).with(:id => asset.id).and_return(asset)
   get :show, :id => asset.id
   assigns[:asset].should == asset
 end
end

HTH,
Matt

Matt,

Thanks for the tip. I didn't know about some of these RSpec tricks. I was able to model the following like this...

Controller behavior:
        current_user.account.assets.find(params[:id])

With the following:
        asset = double('asset')
        asset.stub(:find).with('37').and_return(@mock_asset)
        user = double('user', {:account => double('account', {:assets => asset})})
        user.stub(:role?).with(:site_admin).and_return(true)
        controller.stub(:current_user) { user }

This allows for testing when I have Devise and CanCan for authentication.

All that is left is to make that reusable for all my action tests.

Thanks!

-Mark E.
Reply all
Reply to author
Forward
0 new messages