DRYing up your CRUD controller RSpecs

A lot of what we do in Rails boils down to simple Crud. If you’re in the habit of developing admin sections to allow your clients to control the front end of their site, you’ll probably have noticed that these controllers in particular tend to all look the same. There are quite a few ways to DRY up the controller itself – using something like make_resourceful, for example, but what about your RSpec files?

Robby Russell recently posted a short article about RSpec’s Shared Example Groups. Take a look at his post or at the RSpec documentation (it’s not scary) to see how they work in more detail, but they basically allow you to share your it “should” do ….. end blocks, enabling them to be reused multiple times.

When you think about it, every time we spec a basic CRUD controller, we’re doing the same thing – we should be able to just do something like this:

require File.dirname(__FILE__) + '/../../spec_helper'

describe Admin::ContactsController do
 before(:each) do
   @model = 'Contact'
   login_as_admin
 end

 it_should_behave_like "CRUD GET index"
 it_should_behave_like "CRUD GET show"
 it_should_behave_like "CRUD POST create"
 it_should_behave_like "CRUD PUT update"
 it_should_behave_like "CRUD DELETE destroy"
 it_should_behave_like "CRUD GET edit"
end

Well luckily for you we can! I’ve been using this pattern with most of my CRUD-based controllers for a while now. You just set up the model’s name at the top (and in the case above perform the login_as_admin helper method to log the user in), and for each action in the controller use a specialised shared example group. The example groups all know how to understand your @model definition in before(:each) and map it to the various expectations that the CRUD specs run.

The great thing about this approach is that you can just dump these into your spec file and update them later if you need to. For example if you need to paginate the index action instead of the default find(:all) the shared example group will test, just remove the it_should_behave_like(“CRUD GET index”) and add your own describe block – the rest of the it_should_behave_like lines can stay as they are.

This approach works especially well if you take the approach to CRUD controllers where you create a CrudController class and subclass your CRUD controllers from it – e.g. from the example above:

class Admin::ContactsController < Admin::CrudController

end

My Admin::CrudController reflects on the name of the ContactsController subclass here and figures everything out without me having to do any work. This works because almost all of my Admin section code works the same way. If I need to diverge from the default CRUD behaviour, I just redefine the particular action in Admin::ContactsController:

class Admin::ContactsController < Admin::CrudController
  def index
    #my alternative implementation
  end
end

I personally prefer this approach over the more declarative alternatives such as make_resourceful because I feel more in control this way. That said, I’m open to persuasion :)

Anyway the code for the shared example groups is on Github at http://github.com/edspencer/rspec-crud-controller-shared-example-groups. There’s only one file there – just chuck in in your spec directory and add this line to spec_helper.rb:

require File.expand_path(File.dirname(__FILE__) + "/crud_controller_matchers")

Apart from the magic going on in the CrudSetup module at the top of that file, there’s nothing special going on here, so you can tweak the examples to your particular approach. There’s a good chance some of that code could be written more cleanly so please feel free to suggest changes / fork the file on Github.

UPDATE – Actually no, ignore the FUD about make_resourceful above, it works remarkably well with very few modifications to the shared groups – I’ll post those up as soon as I’m done.

2 Responses to DRYing up your CRUD controller RSpecs

  1. P@ says:

    Hi,I like this – and I want to use it. I have a problem though (well, many of them, another being that I am pretty new to Ruby, but this one relates to your code!)You set @model to the string representation of your model class. And the first thing you do with @model is send it ‘classify’String doesn’t have a classify method (well… not in the version I am using) – so I have come to the conclusion you are expecting arequire ‘ActiveSupport’somewhere in the code.

  2. David Parry says:

    Any chance you could give us an example of the Controller that you’re using that passes your tests? I’ve tried your tests on some basically-generated scaffolding controllers, and there are a lot of failures… thing is, the failures look like stuff I should be checking in my controllers, too (like, edit with invalid ID redirects to index, etc…).

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 2,605 other followers

%d bloggers like this: