Posts Tagged ‘macros’

Route Testing with Shoulda

January 27, 2010

I received a comment in a recent article, by someone who saw the default routes in my config/routes.rb example and suggested I remove them. Yes! This makes total sense from several angles.

Between RESTful routes and named routes, there’s no good reason not to explicitly name all of your routes. Also, you don’t get the goodness of say, “pets_path” or “pet_path(@pet)” if you just let the default routes handle PetsController for you. Finally, if you’re doing TDD (test-driven development) then why aren’t you testing your routes as well?

Route testing is easy, and the tests themselves run super fast. One app of mine has almost 600 routing assertions that run in just 2 seconds. It’s fun to watch the dots for your routing tests zoom across the screen! Let’s start by adding a test file to our system with the standard boilerplate:

# test/unit/routing_test.rb
require 'test_helper'

class RoutingTest < ActionController::TestCase

end

We’ll start with some TestUnit basics before we get into how Shoulda can make life easier. There are three assertions to learn:

def test_generates_user_index
  assert_generates '/users', :controller => 'users', :action => 'index'
end

def test_recognizes_user_index
  assert_recognizes {:controller => 'users', :action => 'index'}, '/users'
end

def test_routes_user_index
  assert_routing '/users', :controller => 'users', :action => 'index'
end

The first assertion, assert_generates, verifies that if url generators like url_for are passed this hash of controllers, actions and whatever else, the correct route (/users in this case) is generated. The next assertion, assert_recognizes, does just the opposite, ensuring that a route of /users end up calling the index action of the users controller. The third version (assert_routing) does both! It combines the two previous assertions into one, and this is what you’ll want most of the time.

Using Shoulda to DRY up your tests

Here are the above tests, in Shoulda form:

require 'test_helper'

class RoutingTest < ActiveSupport::TestCase
  context "routing users" do
    should "generate /users" do
      assert_generates '/users', :controller => 'users', :action => 'index'
    end

    should "recognize users" do
      assert_recognizes {:controller => 'users', :action => 'index'}, '/users'
    end
  
    should "generate and recognize users" do
      assert_routing '/users', :controller => 'users', :action => 'index'
    end
  end
end

While these tests are simple, they’re not very DRY. Just imagine, you’ll need to have seven of these tests *just* for the most basic RESTful route. Instead, install my shoulda routing macros:

script/plugin install git@github.com:bellmyer/shoulda_routing_macros.git

Now here’s an example routing file:

# config/routes.rb
ActionController::Routing::Routes.draw do |map|
  # a simple resource
  map.resources :chickens

  # a resource with extra actions
  map.resources :users, :collection => {:thankyou => :get}, :member => {:profile => :get}

  # nested resources
  map.resources :owners do |owners|
    owners.resources :pets
  end

  # singleton resource
  map.resource :session
  map.ltp_contact_link '/ltp/:code', :controller => 'ltp_contacts', :action => 'new'
end

And this is how easy they are to test:

# test/unit/routing_test.rb
require 'test_helper'

class RoutingTest < ActionController::TestCase
  # simple
  should_map_resources :chickens

  # with extra actions
  should_map_resources :users, :collection => {:thankyou => :get}, :member => {:profile => :get}

  # nested
  should_map_resources :owners
  should_map_nested_resources :owners, :pets

  # singleton
  should_map_resource :session
end

There’s no longer a good excuse to use default routes, or not test your routes in detail. Enjoy!

Advertisements

Shoulda Macro for a Cleaner Uniqueness Test

January 19, 2010

Shoulda macros are so neat and tidy, aren’t they? I love kicking off my unit tests with quick-and-deadly validation and association tests. Here’s an example:

require 'test_helper'

class CouponTest < ActiveSupport::TestCase
  should_validate_presence_of :code, :name, :description

  context "validating uniqueness" do
    setup do
      Factory :coupon
    end

    should_validate_uniqueness_of :code
  end

  should_belong_to :user
end

Do you see anything wrong with this picture? should_validate_uniqueness_of necessarily requires that you already have a record in the database. As much as I hate database interaction in my tests, it’s a necessary evil here. The macro works by copying the attributes of an existing record, and validating it to see if you get any uniqueness errors. So, for this one test, I have to setup a context with a Factory call, because I’m not about to create a record for all the other tests that don’t need it.

Wouldn’t it be nice if you could call should_validate_uniqueness_of and it would create the record for you, if one didn’t already exist? It could use the Factory you’ve already setup. And it would still work the old way if you don’t have factories, or don’t enjoy DRY coding.

Add this macro to your application:

# test/shoulda_macros/validation_macros.rb
module Test
  module Unit
    class TestCase
      class << self
        alias_method :svuo_original, :should_validate_uniqueness_of
        
        def should_validate_uniqueness_of(*attributes)
          class_name = self.name.gsub(/Test$/, '')
          klass = class_name.constantize
          model_sym = class_name.underscore.to_sym
          context "with a record in the database" do
            setup do
              Factory model_sym unless klass.count > 1
            end
            
            svuo_original *attributes
          end
        end
      end
    end
  end
end

Here, we’re overriding the default Shoulda macro. Before we call the original, we’ll setup a context and create the record using our Factory, unless a record is already created. That means it will always “just work”, with the exact same syntax, and all of the options of the original should_validate_uniqueness_of

Thoughtbot can’t create the macro like this by default, because it assumes you have factory_girl installed. But if you do use factories, this upgrade will take some of the ugly out of your tests:

require 'test_helper'

class CouponTest &lt; ActiveSupport::TestCase
  should_validate_presence_of :code, :name, :description
  should_validate_uniqueness_of :code

  should_belong_to :user
end

Nice, eh? As usual, all you need to do to install/create a new shoulda macro is drop a ruby file into the test/shoulda_macros folder, with macro methods defined. You don’t even need to reopen TestCase the way I did, unless you’re overriding an existing macro.

undefined local variable or method `should_act_as_list’

December 26, 2009

I got this error today, which was a surprise because I was porting over good, tested code from an old version of an application. To recreate the error after solving, I built a simple test app with a single model, SantasList. The test file looks like this:

require 'test_helper'

class SantasListTest < ActiveSupport::TestCase
  should_act_as_list
end

At first I suspected a missing gem, but environments/test.rb showed this:

  config.gem 'thoughtbot-shoulda', :lib => 'shoulda', :source => 'http://gems.github.com'
  config.gem 'seanhussey-woulda', :lib => 'woulda', :source => 'http://gems.github.com'

I half suspected that woulda wasn’t loading properly, and I was half right. I started to trace the problem in the gem itself, which I’d wisely unpacked into the application. Inside the woulda gem, I found the contents of shoulda_macros/woulda_macros.rb:

require 'woulda'

Simple enough. So in lib/woulda.rb I found the line that loads the acts_as_list macro:

  require "#{woulda_dir}/acts_as_list" if defined? ActiveRecord::Acts::List

Voila!* First, looking at the whole file shows that the author was smart, and only loads the macros that might actually be used. Second, I’ve found my problem: I don’t have the acts_as_list plugin installed! No plugin, no macro. So I was half right – the macro wasn’t loading, but it was my fault. As I said, I’m porting the application to a new code base, and I hadn’t needed this plugin yet. I’m thrilled the problem was so easy to find, and Sean Hussey’s clean code certainly helped.

As a side pitch, if you’re not using woulda yet, I highly recommend it. For shoulda users, it provides macros for the most common ActiveRecord plugins like acts_as_list, acts_as_taggable, will_paginate, paperclip, acts_as_state_machine, and more. And if you’re not familiar with shoulda macros, then why not??

*I like to say “viola” as a joke, but in print it just looks like a typo.