Living on the Edge

Stubbing Authentication in Your Controllers

Posted on April 06, 2007

I was trying to spec out a few of my controllers that had actions on them requiring authentication. After jumping through many mind hoops to figure out how to stub them out properly I asked the RSpec-Users list and received this solution from Graeme Nelson:

def mock_user_authentication(allow_user_to_pass=true)       
  controller.stub!(:login_required).and_return(allow_user_to_pass)
end

It’s elegantly simple and works well. I’m still fumbling around with this RSpec stuff and did not realize I could stub out a method directly on the controller. This has cleared up a big missing piece in my thinking.

Mocking RESTful Routes

Posted on March 27, 2007

I just spent about an hour trying to figure out why none of my RESTful routes were working properly within my RSpec controller specs. In my controller I had some boilerplate code like this:

# POST /categories
# POST /categories.xml
def create
  @category = Category.new(params[:category])

  respond_to do |format|
    if @category.save
      flash[:notice] = 'Category was successfully created.'
      format.html { redirect_to category_url(@category) }
      format.xml  { head :created, :location => category_url(@category) }
    else
      format.html { render :action => "new" }
      format.xml  { render :xml => @category.errors.to_xml }
    end
  end
end

The line causing the problem was:

redirect_to category_url(@category)

I kept receiving an error on the eval of category_url with an error description of “can’t convert Fixnum into String”.

I tried replacing @category with @category.id to see if I would get different results. The error went away but the test failed indicating that the id returned from the @category instance was not the same as I was expecting. This led me to determine that I needed to stub out the id property on my class. So I added the following to my setup:

@category.stub!(:id).and_return(1)

Everything worked. Problem solved. But wait, that’s ugly and smells of something wrong. I should be able to just pass the object to the category_url and have it return the correct value. What I did next was go down a rat hole trying to figure out what the named route was sending to the object to get the id. I had assumed id, but in fact it’s to_param, which I had already stubbed out as follows:

@category = mock_model(Category, :to_param => 1)

So what’s the problem? It turns out that to_param must return a string. Makes sense. I changed it to the following and everything worked perfectly:

@category = mock_model(Category, :to_param => "1")

It’s little things like this that make learning so much fun. This issue is really indicative of a much bigger problem—my lack of understanding mocks and stubs. But, I’ll have more to write about this later.

Overtesting, Who Cares?

Posted on March 18, 2007
As I indicated in a prior post I’m beginning to wrap my head around Rspec and use it in a new project that I’m working on. I recently came across a post where someone had written something similar to the following, indicating a spec for has_many and belongs_to in a model.
context "A Category with fixtures loaded" do
  fixtures :listings, :categories

  specify "should have many Listings" do
    l = categories(:cars).listings
    l.should_not be_nil
    l.should_not be_empty
    l.first.should be_an_instance_of Listing
  end
end

One of the comments to the blog post was critical of this approach suggesting that this spec was validating Rails code and that the author should focus only on code that he / she has written. I’ve seen this argument several times in the past and this issue actually came up briefly in the Advanced Rails Training course in Chicago.

While I agree that you shouldn’t be testing Rails code, that’s not what is going on here. The spec is for the existence of the defined relationships within the model, and that things are wired up properly to enable accessing the relationship properly within the model. If someone were to inadvertently remove the has_many method call in the model the spec would fail, which is exactly the behavior we want.

Secondly, even if we were testing Rails code, I think it’s better to error on the side of overcoverage than to not write tests at all. It is important that developers are not so overwhelmed with the “right” or “wrong” way to do testing that testing is not done at all.

The above code actually comes from a project I’m working on. This blog post aside, if you have recommendations on how it should be done differently, please let me know.

Working with Test::Rails

Posted on March 17, 2007

The past couple of weeks I’ve been digging into Test::Rails from the ZenTest library of tools. One of the main benefits to using Test::Rails is the ability to have separate Controller and View tests. It also includes a lot of test helpers that make the process of testing Views and Controllers much easier. I about gave up on Test::Rails a couple of different times; the docs are a bit sparse and there’s not a lot information on the internet about how to setup and use Test::Rails. I finally happened upon a blog post that gave me a few tidbits of information and got me pointed in the right direction.

I’ve documented the setup process I took as well as some helpful hints.

Topfunky Power Tools Plugin

Posted on February 27, 2007

I recently started using Topfunky’s Power Tools Plugin to clean up some of my testing routines. It’s a nice little package that brings together lots of different asserts that have been out there in the wild in one form or another.

I really enjoy the assert_required_fields method. I used to implement my model checking like so:

def test_should_require_login
  assert_no_difference User, :count do
    u = create_user(:login => nil)
    assert u.errors.on(:login)
  end
end

...and now with the Topfunky Power Tools Plugin I end up with the following:

def test_should_require_login_password
  assert_required_fields :create_user, :login, :password
end

Notice how it allows you to check multiple fields at once. There’s a lot more available in the plugin, but sometimes all it takes is one or two little things to make your day.