Specifying a before_filter and/or private method 1

Posted by daniel Thu, 04 Oct 2007 12:12:00 GMT

How do you write specs for a private or protected method? Specifically (as this is often the case), how do you write a spec for a before_filter?

The answers that I found while looking online were not very explicit. The main points seemed to be “don’t test private/protected method”, “you should be specifying expected behaviours in the places where they’re used.” I decided to write this article to clarify how I’ve interpreted that advice.

Essentially, I decided to follow the advice given online. Don’t test the methods, test the behaviours you expect. Let’s take a simple example and see what this means in practice.

For our facebook application, I have a facebook controller which handles all the facebook posts (very RESTless, but that’s facebook for you!). There are two things that I want to see happen with every authenticated facebook action, and I’ve defined a filter for each of them:

class FacebookController < ApplicationController
  before_filter :require_facebook_install
  before_filter :set_user

  def index
    # ...
  end

  def other_action
    # ...
  end

private
  def set_user
    if @user = User.find_by_facebook_uid(fbparams["user"], :include => [:interactions])
      if fbparams["friends"]
        @user.cached_friend_uids = fbparams["friends"]
        @user.save
      end
      if fbparams["session_key"]
        @user.facebook_session_key = fbparams["session_key"]
        @user.save
      end
    end
  end

end

Now, requirefacebookinstall is given by RFacebook, so all I want to specify is that for certain actions, this filter must be called. All that it does is return true if proper parameters are included, that represent a valid facebook session. There’s a lot of those parameters, and I only use a few of them. The only time when I might want to have this filter return false is if I was testing what would happen with a blank session. However, this should never happen in my specifications, since I’m not trying to spec RFacebook, but my own code. This is the simple, shared spec that I wrote:

describe "Facebook Action", :shared => true do
  include FacebookControllerSpecHelper

  before(:each) do
    @controller.should_receive(:require_facebook_install).and_return(true)
  end

end

It’s not much to look at, and it doesn’t need to be! How to use it? Just include this (which I’d like to call a partial spec, but the rspec keyword is “shared”) into other specs:

describe FacebookController, "index page" do
  include FacebookControllerSpecHelper

  it_should_behave_like "Facebook Action"

  before(:each) do
    @user = User.create(:facebook_uid => 1)
  end

  def do_post
    post :index
  end

  it "should render the canvas" do
    do_post
  end

end

I can do this for any of the other facebook “pages”, of course. So far so good. Adding this “itshouldbehavelike” directive ensures that the filter is called for the actions where I specify that it needs to be called. Handily enough, this also removes the need for me to mock requirefacebook_install in every one of those specs, since it’s mocked just once in the shared spec.

How about setuser? That’s a little bit more tricky. In this case, I have actually written setuser myself, so I need to specify it. But I can’t! It’s a private method, therefore invisible to my specs! Oh dear. Let’s go back to the initial advice. Don’t specify methods, specify behaviours where they’re used. Okay. Thinking about it on a basic level, this makes sense. I do want to specify that my filter is working for each of the actions I’ve told it to work for. One way to do this would be to specify that set_user must function properly in each of those actions. The smart way is of course to put that into a partial spec as well.

describe "Facebook Action With User", :shared => true do
  include FacebookControllerSpecHelper

  before(:each) do
    @user = User.create(:facebook_uid => valid_fbparams["user"])

    @controller.should_receive(:fbparams).at_least(:once).and_return(valid_fbparams)
    User.should_receive(:find_by_facebook_uid).at_least(:once).and_return(@user)
  end

  it "should call set_user" do
    do_post
  end

  it "should save the user's cached friends" do
    @user.should_receive(:cached_friend_uids=).with(valid_fbparams["friends"])
    @user.should_receive(:save).at_least(:once)

    do_post
  end

  it "should save the user's session key" do
    @user.should_receive(:facebook_session_key=).with(valid_fbparams["session_key"])
    @user.should_receive(:save).at_least(:once)

    do_post
  end
end

Those are the three things I expect my setuser beforefilter to do for me. So now I can include them in my controller action spec as well!

describe FacebookController, "index page" do
  include FacebookControllerSpecHelper

  it_should_behave_like "Facebook Action"
  it_should_behave_like "Facebook Action With User"

  def do_post
    post :index
  end

  it "should render the canvas" do
    @controller.instance_variable_set(:@user, @user)
    do_post
  end

end

And that’s all. The best part is, including the “Facebook Action With User” behaviour automatically mocks that part of the process for me in all the actions where I put it, so that I don’t need to do it again and again. By extracting those apparently intractable filters into their own shared spec, I’ve not only managed to spec them, but I’ve also reduced the clutter in my mocking code. I now only mock the things which are relevant to the current action, instead of having to mock everything to “make things work”.

On the downside, by DRYing up this code it’s become a tad harder to follow at a glance, but I think it’s more readable once you’ve figured out what the included shared specs provide.

One final caveat: if you mock something in your shared specs, don’t mock it again in your concrete specs. Otherwise, you will get errors about your shared spec being broken.

I hope you found this article useful. Please feel free to leave a comment below if you agree or disagree with this method - particularly if you have something better to suggest!

Please vote this article up on social news sites! Why?

Stumble It!
Trackbacks

Use the following link to trackback from your own site:
http://inter-sections.net/trackbacks?article_id=specifying-a-before_filter-and-or-private-method&day=04&month=10&year=2007

Comments

Leave a comment

  1. Avatar
    Vitor Pellegrino 2 months later:

    Great Article, just what i needed to do! Thanks for sharing your thoughts!

Comments