Skip to content

Latest commit

 

History

History
270 lines (207 loc) · 11.3 KB

README.rdoc

File metadata and controls

270 lines (207 loc) · 11.3 KB

Skinny Spec

Skinny Spec is a collection of spec helper methods designed to help trim the fat and DRY up some of the bloat that sometimes results from properly specing your classes and templates.

Requirements and Recommendations

Obviously you’ll need to be using RSpec and Rspec-Rails as your testing framework.

Skinny Spec was originally designed [and best enjoyed] if you’re using Haml and make_resourceful but will default to ERb and a facsimile of Rails’ default scaffolding [for the views and controllers, respectively] if Haml and/or make_resourceful are not available. I recommend using them though. :)

In addition, Skinny Spec uses Ruby2Ruby to make nicer expectation messages and you’ll want to have that installed as well. It’s not a dependency or anything but it is highly recommended.

Setup

Once you’ve installed the plugin in your app’s vendor/plugins folder, you’re ready to rock! Skinny Spec includes itself into the proper RSpec classes so there’s no configuration on your part. Sweet!

Usage

The simplest way to use Skinny Specs is to generate a resource scaffold:

script/generate skinny_scaffold User

This command takes the usual complement of attribute definitions like script/generate scaffold. Then have a look at the generated files (particularly the specs) to see what’s new and different with Skinny Spec.

Controller Specs

Let’s look at the controller specs.

describe UsersController do
  describe "GET :index" do
    before(:each) do
      @users = stub_index(User)
    end

    it_should_find_and_assign :users
    it_should_render :template, "index"
  end

  # ...

  describe "POST :create" do
    describe "when successful" do
      before(:each) do
        @user = stub_create(User)
      end

      it_should_initialize_and_save :user
      it_should_redirect_to { user_url(@user) }
    end

  # ...

First thing you should see is an example group for GET :index. That stub_index method there does a lot of work behind the curtain. I’ll leave it up to you to check the documentation for it (and its brothers and sister methods like stub_new) but I will point out that the methods named stub_controller_method should only be used for stubbing and mocking the main object of the method. To create mocks for other ancillary objects, please use stub_find_all, stub_find_one, and stub_initialize. The reason for this is because the former methods actually save us a step by defining an implicit controller method request. If you add a new method to your resource routing, you’ll want to use the helper method define_request in those example groups to define an explicit request, like so:

describe "PUT :demote" do
  define_request { put :demote }

  # ...
end

You can also define a method called shared_request to “share” a define_request across nested describe blocks, like so:

describe "POST :create" do
  def shared_request
    post :create
  end

  describe "when successful" do
    # ...
  end

  describe "when unsuccessful" do
    # ...
  end
end

Note: When you’re adding longer, more complicated controller specs you can still leverage implicit and explicit requests by calling do_request in your spec as in the following example:

# Note this controller is UsersController and _not_ CategoriesController
# and that loading the categories isn't part of the default actions
# and cannot use the <tt>stub_<i>controller_method</i></tt> helpers
# [which create implicit requests based on the controller method in the name]
# but uses <tt>stub_find_all</tt> instead
describe "GET :new" do
  before(:each) do
    @user = stub_new(User)
    @categories = stub_find_all(Category)
  end

  # ...

  it "should preload categories" do
    Category.should_receive(:find).with(:all)
    do_request
  end

  it "should assign @categories" do
    do_request
    assigns[:categories].should == @categories
  end
end

Finally we get to the meat of the spec and of Skinny Specs itself: the actual expectations. The first thing you’ll notice is the use of example group (read: “describe” block) level methods instead of the usual example (read: “it”) blocks. Using this helper at the example group level saves us three lines over using an example block. (If this isn’t significant to you, this is probably the wrong plugin for you as well. Sorry.) Note that none of these methods use the instance variables defined in the “before” block because they are all nil at the example block level. Let’s look at a sample method to see how it works:

it_should_find_and_assign :users

This actually wraps two different expectations: one that User.should_receive(:find).with(:all) and another that the instance variable @users is assigned with the return value from that finder call. If you need to add more detailed arguments to the find, you can easily break this into two different expectations like:

it_should_find :users, :limit => 2
it_should_assign :users

See the documentation for the it_should_find for more information. You might have guessed that it_should_initialize_assign and it_should_render_template work in a similar fashion and you’d be right. Again, see the documentation for these individual methods for more information. Lots of information in those docs.

A useful helper method that doesn’t appear in any of the scaffolding is with_default_restful_actions which takes a block and evaluates it for each of the RESTful controller actions. Very useful for spec’ing that these methods redirect to the login page when the user isn’t logged in, for example. This method is designed to be used inside an example like so:

describe "when not logged in" do
  it "should redirect all requests to the login page" do
    with_default_restful_actions do
      response.should redirect_to(login_url)
    end
  end
end

Before we’re through with the controller specs, let me point out one more important detail. In order to use it_should_redirect_to we have to send the routing inside a block argument there so it can be evaluated in the example context instead of the example group, where it completely blows up. This methodology is used anywhere routing is referred to in a “skinny”, example group level spec.

View Specs

Now let’s move to the view specs!

describe "/users/form.html.haml" do
  before(:each) do
    @user = mock_and_assign(User, :stub => {
      :name => "foo",
      :birthday => 1.week.ago,
      :adult => false
    })
  end

  it_should_have_form_for :user

  it_should_allow_editing :user, :name
  it_should_allow_editing :user, :birthday
  it_should_allow_editing :user, :adult

  it_should_link_to_show :user
  it_should_link_to { users_path }
end

Like the special stub_index methods in the controller specs, the view specs have a shorthand mock and stub helpers: mock_and_assign and mock_and_assign_collection. These are well documented so please check them out.

There are also some really nice helper methods that I’d like point out. First is it_should_have_form_for. This is a really good convenience wrapper that basically wraps the much longer:

it "should use form_for to generate the proper form action and options" do
  template.should_receive(:form_for).with(@user)
  do_render
end

Next up is the it_should_allow_editing helper. I love this method the most because it really helps DRY up that view spec while at the same time being amazingly unbrittle. Instead of creating an expectation for a specific form element, this method creates a generalized expectation that there’s a form element with the name attribute set in such away that it will generate the proper params to use in the controller to edit or create the instance. Check out the docs and the source for more information on this. Also check out it_should_have_form_element_for which is roughly equivalent for those times when you use form_tag instead.

Finally let’s look at those it_should_link_to_controller_method helpers. These methods (and there’s one each for the controller methods new, edit, show, and delete) point to instance variables which you should be created in the “before” blocks with mock_and_assign. The other is it_should_allow_editing which is likewise covered extensively in the documentation and I will just point out here that, like it_should_link_to_edit and such, it takes a symbol for the name of the instance variable it refers to and additionally takes a symbol for the name of the attribute to be edited.

Also note that, when constructing a long form example, instead of defining an instance variable for the name of the template and calling render @that_template you can simply call do_render which takes the name of the template from the outermost example group where it is customarily stated.

Model Specs

Skinny Spec adds a matcher for the various ActiveRecord associations. On the example group level you call them like:

it_should_belong_to :manager
it_should_have_many :clients

Within an example you can call them on either the class or the instance setup in the “before” block. These are equivalent:

@user.should belong_to(:group)
User.should belong_to(:group)

I’ve also added some very basic validation helpers like it_should_validate_presence_of, it_should_validate_uniqueness_of, it_should_not_mass_assign. Please consult the documentation for more information.

Miscellaneous Notes

In the scaffolding, I have used my own idiomatic Rails usage:

  • All controller actions which use HTML forms [new, edit, etc] use a shared form and leverage form_for to its fullest by letting it create the appropriate action and options.

  • Some instances where you might expect link_to are button_to. This is to provide a common interface element which can be styled the same instead of a mishmash of links and buttons and inputs everywhere. To take full advantage of this, I usually override many of Rails’ default helpers with custom ones that all use actual HTML BUTTON elements which are much easier to style than “button” typed INPUT. I’ve provided a text file in the “additional” folder of this plugin which you can use in your ApplicationHelper. (I also provide an optional override helper for the label method which uses #titleize instead of humanize for stylistic reasons).

  • Probably more that I can’t think of.

Credits and Thanks

Sections of this code were taken from or inspired by Rick Olsen’s rspec_on_rails_on_crack. Also thanks and props to Hampton Catlin and Nathan Weizenbaum for the lovely and imminently useable Haml and make_resourceful. Also also praises and glory to David Chelimsky and the Rspec crew.

Also thanks to Don Petersen for his suggestions and fixes.