Posts Tagged ‘shoulda’

Double-Blind Test-Driven Development in Rails 3: Part 1

January 31, 2011

This is a three-part series introducing the concept of double-blind test-driven development in Rails. This post defines the concept itself, and lays the groundwork by showing the way tests are more commonly written. The next couple posts will show how to double-blind test various common rails elements, and how to make this added layer of protection automatic and quick.

  1. Simple Tests
  2. Double-Blind Tests
  3. Making it Practical with RSpec Matchers

Looking at a rails application that was built with test-driven development, you might expect to see something like this:

# spec/models/teacher_spec.rb

describe Teacher do
  it "has many subjects" do
    teacher = Factory.create :teacher
    subject = teacher.subjects.create Factory.attributes_for(:subject)

    teacher.subjects.should include(subject)
  end
  
  describe "name" do
    it "is present" do
      teacher = Teacher.new

      teacher.should_not be_valid
      teacher.errors[:name].should include("can't be blank")
    end
    
    it "is at most 50 characters" do
      teacher = Teacher.new :name => 'x' * 51
      
      teacher.should_not be_valid
      teacher.errors[:name].should include("must be 50 characters or less")
    end
  end
end

Truth be told, if you’re seeing this in the wild the app is probably doing pretty good. This level of testing works great during the early stages of an app, when things are simple. But as things grow and/or multiple developers become involved, you need more.

Consider models where the associations and validations stretch into the dozens of lines. The more careful and specific you are about validations, the easier it is to get conflicting or overlapping validations. I actually came up with the concept of double-blind testing while retro-testing models in a client app that previously had no validation specs.

What is Double-Blind Testing?

In the world of scientific studies, you always need a control group. One set of participants gets the latest and greatest new diet pill, while the other gets a placebo. Researchers used to think this was good enough, and probably pretty funny to watch the placebo users rave about their shrinking waistlines. But it turns out studies like this still allowed some bias – as researchers observed the effects, their *own* preconceived notions tainted results. Enter the double-blind study.

In a double-blind study, the researchers themselves are unaware of which participants are in the control group, and which are being tested. Both sides are “blind”. They may have lost funny patient anecdotes, but they gained research reliability.

Applying the Lessons of Double-Blind Studies to Test-Driven Development

As I said, in the early stages of an app the tests I showed above work great, as long as you’re using TDD and the red-green-refactor cycle. This means you write the test, run it, and it fails. Then you write the simplest code that will make the test pass, run the test again, and confirm that it passes. Most testing tools will literally show red or green as you do this. Then, as you start to amass tests, you’re free to refactor your code (abstracting common code into helper methods, changing for readability, etc) and run the tests again at any time. You will see failures if you broke anything. If not, you’ve more or less guaranteed your code refactoring works properly.

The problem comes in when you start changing old code, or adding tests to processes that didn’t initially happen. What I’m calling double-blind testing is this:

each test needs to verify the object’s behavior before testing what changes.

As an example, let’s rewrite one of the tests from above:

# original test

describe "name" do
  it "is present" do
    teacher = Teacher.new

    teacher.should_not be_valid
    teacher.errors[:name].should include("can't be blank")
  end
end
# modified to be double-blind

describe "name" do
  it "is present" do
    error_message = "can't be blank"

    teacher = Teacher.new :name => 'Joe Example'
    teacher.valid?
    teacher.errors[:name].should_not include(error_message)

    teacher.name = nil
    teacher.should_not be_valid
    teacher.errors[:name].should include(error_message)

    teacher.name = ""
    teacher.should_not be_valid
    teacher.errors[:name].should include(error_message)
  end
end

This is the basic pattern for all double-blind testing. We’re not leaving anything to chance. In the original version, we expected our object to be invalid, we treated it as such, and we got the result we expected. Do you see the problem with this?

Here’s an exercise: can you make the original test pass, even though the object validation is not working correctly? There’s actually a style of pair programming that routinely does exactly this. One developer writes the test, and the other writes just enough code to make it pass, with the good-natured intention of tripping up the first developer whenever possible. If you wrote the original test, I could satisfy it by just adding the error message to every record on validation, regardless of whether it’s true! Your test would pass, but the app would fail.

The test is now “double-blind” in the sense that we as testers have factored out our own expectations from the test. In this case, we expect the error message to not be there until we initialize the object a certain way, and this can be bad. It may sound far-fetched or paranoid*, but in large codebases your original tests are often abused in this very way. The “you” that writes new code today is often at odds with the “you” from three months ago that wrote the older code with a different understanding of the problem at hand.

*Plus, everybody knows it’s not paranoia when the world really is out to get you. I’ve discussed this at length with the voices in my head, and they all agree. Except Javier. That guy’s a jerk.

Now that I’ve laid out the justification, let’s take a closer look at how the test changed. The first thing I did was create a version of the object that I believe should NOT trigger the error message. Then I run through two cases that should. You can see right away, I was forced to be more *specific* about what should trigger an error. Instead of just a blank object with no values set, I’ve proactively set the attribute in question to both nil and blank. A key element here is to try to work with the *same* object, modifying between tests, rather than creating a new object each time. My test wouldn’t have been as specific if I’d just recreated a blank Teacher object and run a single validation check.

Also, with the increased code comes the increased chance of typos. We don’t want to DRY test code up too much, because a good rule is to keep your tests are readable (non-abstract) as possible. But I’ve specified the error message at the top of the test, and reused that string over and over. I did this in a way that DRY’s the code and adds readability. You can see at a glance that all three tests are checking for the same error.

Finally, the first time I run the object’s validation, notice I’m not asserting that it should be valid. If I had written teacher.should be_valid on line 8 of the double-blind test, I’d have to take the extra time to make sure every other part of the object was valid. Not only is this time-consuming, it’s very brittle. Any future validations would break this test.

If you use factories often, you may suggest setting it up that way since a factory-generated object should always be valid. Then you could assert validity. However, this only slows down your test suite. it’s enough just to run valid? on the object, which triggers all the validation checks to load up our errors hash.

Summary

I believe this is a new concept – I was already coding most of my tests this way, but it didn’t dawn on me how valuable it was until I started retro-testing previously testless code. The value showed itself right away.

I would love to hear feedback on this – if you think it’s unnecessary (I tend to be very rainman-ish about my testing code) or even detrimental. However, if you think it’s too much work, I ask you to hold your criticism until you’ve read part 3 of this article, where I show how to use your own RSpec matchers to greatly speed this process.

Advertisements

Introducing: Rails Kata

June 24, 2010

A Scary Idea is Born

In a previous post about RailsConf and Code Kata, I decided to take action, and begin publicly practicing my programming craft. Without further ado, I give you my brainchild: I’ve designed my first personal kata.

I’ve been meaning to get into the Cucumber testing framework for a long time, so my first kata was to create an entire MVC stack, test driven, using Cucumber and Shoulda. I recorded it. It took 100 minutes. So I went back to the drawing board for something more fundamental – basic data modeling with my chosen testing framework and editing tools. Voila, a ten minute kata practicing the most fundamental rails tools.

I’m posting my work on Vimeo, due to their unlimited video lengths. I invite you to record your own version of this kata, and post the video link in the comments! Let’s learn from each other.

So here’s my first try. I’m not good at screencasting just yet. I also have a sore throat, making my narration a little scratchy. But I welcome feedback.

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!

Restful Controller Tests with Shoulda – Stubbing

January 20, 2010

View the Source Code

This is part 2 of a 5 part series on restful controller tests, using Shoulda as the foundation. Here are all of them so far:

  1. The Basics
  2. Stubbing for Speed

Stubbing

My initial set of controller tests are a great foundation. They lay out exactly how to take advantage of Shoulda to create tests for three different roles of user: anonymous visitor, member, and admin. Now we’ll use the Mocha ruby gem to speed up our tests, by eliminating database calls.

Here’s my (amateurish) video of the changes, followed by a more detailed explanation:

We’ll start by ensuring that rails will require the gem. Add this to config/environments/test.rb:

config.gem 'mocha'

Then on the commandline:

rake gems:install RAILS_ENV=test

Mocha allows us to stub (or “fake”) methods on an object, and track how often they’re called during a test. For example, if you stub an object’s save method in a create action, you can test what happens when saving succeeds (returns true) or fails (returns false), with no messy database interaction.

While I won’t get into a primer on Mocha itself, I will say that almost every database interaction can be removed from functional tests, with the exception of user authentication. I usually leave that in, and that’s my one and only use for fixtures. Fixtures are fast, but cumbersome if you begin adding multiple records for every model in your application. I use factories instead, but they’re not as fast for loading the database. Factories are another chapter in the Functional Tests saga, however.

That said, let’s get on to reviewing the stubbed versions of my tests. Starting with the admin context, our setup changes from this:

    setup do
      @valid = Factory.build(:setting).attributes
      @setting = Factory :setting
      login_as :admin
    end

to this:

    setup do
      @setting = Factory.build :setting
      @setting.id = 1001

      Setting.stubs(:find).returns(@setting)
      Setting.stubs(:find).with(:all, anything).returns([@setting])
      
      login_as :admin
    end

Instead of creating a new setting, we’re using our factory to build an unsaved one. We’re giving it an id since saving would have normally done this. Finally, we’re stubbing out the find method, both for an individual and for all settings, so they return the one we’ve built. Loggin in as admin will be our only database hit.

Now for the actions. The index, show, and new action tests stay exactly the same, except the first two are no longer hitting the database – we’ve stubbed out the find method to automatically return our fake setting. On to the create method, in the “with valid data” context. Here’s the original:

context "with valid data" do
  setup do
    post :create, :setting => @valid
  end
        
  should_assign_to :setting, :class => Setting
  should_redirect_to("setting page"){setting_path(assigns(:setting))}
  should_set_the_flash_to "Setting was successfully created."
        
  should "create the record" do
    assert Setting.find_by_name(@valid['name'])
  end
end

And here’s the stubbed version:

context "with valid data" do
  setup do
    Setting.any_instance.expects(:save).returns(true).once
    Setting.any_instance.stubs(:id).returns(1001)
          
    post :create, :setting => {}
  end

  should_assign_to :setting, :class => Setting
  should_redirect_to("setting page"){setting_path(1001)}
  should_set_the_flash_to "Setting was successfully created."
end

We’ve stubbed any instance of Setting to return true upon save, without actually saving. No database hit, and we still get to test everything. We’ve even dropped our “create the record” test, and stopped passing in valid data, because the expectation we set in the setup handles this. To be more paranoid, we could pass in valid data and check that those params end up in the Setting.new call, but I think that’s overkill since it’s such a basic step.

Next, the “with invalid data” context. First the original:

context "with invalid data" do
  setup do
    post :create, :setting => {}
  end
  
  should_assign_to :setting, :class => Setting
  should_respond_with :success
  should_render_with_layout :settings
  should_render_template :new
  should_not_set_the_flash
end

And the new version:

context "with invalid data" do
  setup do
    Setting.any_instance.expects(:save).returns(false).once
    post :create, :setting => {}
  end
  
  should_assign_to :setting, :class => Setting
  should_respond_with :success
  should_render_with_layout :settings
  should_render_template :new
  should_not_set_the_flash
end

The only changes here are that we’re setting a stubbed expectation of a call to save, forcing a return value of false, and again we don’t need to pass in valid data. Now we can run all the same tests, because we’ve forced the code down the failure branch.

Our edit tests stay the same, with the exception again of no database hit thanks to our stubbed finders. Next up is the update action, and we’ll start with the valid data context. Here’s the original:

context "with valid data" do
  setup do
    put :update, :id => @setting.id, :setting => {:name => 'Bob'}
  end
  
  should_assign_to(:setting){@setting}
  should_redirect_to("setting page"){setting_path(assigns(:setting))}
  should_set_the_flash_to "Setting was successfully updated."
  
  should "update the record" do
    @setting.reload
    assert_equal 'Bob', @setting.name
  end
end

And here’s the new version:

context "with invalid data" do
  setup do
    @setting.expects(:update_attributes).returns(false).once
    put :update, :id => @setting.id, :setting => {}
  end
  
  should_assign_to :setting, :class => Setting
  should_respond_with :success
  should_render_with_layout :settings
  should_render_template :edit
  should_not_set_the_flash
end

Much like a successful create, we stub out our update to succeed and run all the same tests except that the record was actually changed. Now the invalid data context. Here’s the original:

context "with invalid data" do
  setup do
    put :update, :id => @setting.id, :setting => {:name => nil}
  end
  
  should_assign_to :setting, :class => Setting
  should_respond_with :success
  should_render_with_layout :settings
  should_render_template :edit
  should_not_set_the_flash
end

And the new version:

context "with invalid data" do
  setup do
    @setting.expects(:update_attributes).returns(false).once
    put :update, :id => @setting.id, :setting => {}
  end
  
  should_assign_to :setting, :class => Setting
  should_respond_with :success
  should_render_with_layout :settings
  should_render_template :edit
  should_not_set_the_flash
end

And much like our unsuccessful create, we stub out the update to return false, and run the same tests! The finaly action that changes is update, and it’s very easy. First the original:

context "destroying" do
  setup do
    delete :destroy, :id => @setting.id
  end
      
  should_assign_to(:setting){@setting}
  should_redirect_to("index"){settings_path}
  should_not_set_the_flash
      
  should "delete the record" do
    assert !Setting.find_by_id(@setting.id)
  end
end

And the new version:

context "destroying" do
  setup do
    @setting.expects(:destroy).once
    delete :destroy, :id => @setting.id
  end
    
  should_assign_to(:setting){@setting}
  should_redirect_to("index"){settings_path}
  should_not_set_the_flash
end

This time we’re only stubbing out the call to destroy, expecting it to happen once. All the tests stay the same, except we no longer test that a record has actually been removed. No record was actually in the database, so we can’t. Plus, a failing call to destroy is so rare, it doesn’t even let you know in the return value.

The admin context is the only one that changes with these upgrades, members and visitors never get this far in our examples. This will generally make your functional tests over twice as fast, which is huge when you’re faced with mounting test times. I believe I was able to cut testing time down from 90 seconds to just under 30, implementing stubbing across an application’s test suite.

If you view the source code for this part of the series, you’ll see a lot of repetition in the tests. We’ll DRY (Don’t Repeat Yourself) that code in the next chapter in the series.

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.

Restful Controller Tests with Shoulda

December 30, 2009

View the Source Code

This is part 1 of a 5 part series on restful controller tests, using Shoulda as the foundation. Here are all of them so far:

  1. The Basics
  2. Stubbing for Speed

After porting eight MVC stacks from an older application to its newer home, and revamping all the tests along the way, I developed a great rhythm in cranking out functional tests for restful resources. I took the time to use the best that modern shoulda has to offer, and came up with (what I feel) is a great set of functional tests.

First, each controller action gets its own context, with several tests executed in each. But wait, there’s more! Do you have multiple roles? I usually have visitor (not logged in), member (logged in, normal user) and admin (logged in, superuser). That means triple the testing, because I want to confirm that the application is behaving the correct way under each circumstance, and the behavior is often very different.

Next, a little background. For authentication this app is using restful_authentication and role_requirement. My basic testing suite consists of shoulda, factory_girl, and woulda. I’m also using autotest, mocha, test_benchmarker, and redgreen to speed up my tests and development cycle, but I’ll cover those in a different article. This is about the basics.

Let’s begin (finally) with a restful controller for site settings – admins have all access, but members and visitors have none. This will allow me to demonstrate how I handle testing each role, and it’s easy to grant more privileges to a role by copying over the tests from say, admin to member.

Admin Tests

I find it’s easier TDD to start with the least restrictive role and work backward, adding restrictions to my controller as I go along. That said, here are my admin tests. I first setup the admin context like so:

  context "as admin" do
    setup do
      @setting = Factory.create(:setting)
      @valid = Factory.build(:setting).attributes
      login_as :admin
    end

    ...
  end

I’ve created a basic setting (using factories), and a hash of valid attributes I can use for create and update actions. I’ve also logged in as admin. If you don’t understand contexts and setup, a context is a group of tests, and the setup for that context is done before every test within. Contexts can also be nested – and they will be.

Index

  context "getting index" do
    setup do
      get :index
    end
      
    should_assign_to(:settings){[@setting]}
    should_respond_with :success
    should_render_with_layout
    should_render_template :index
    should_not_set_the_flash
  end

Using shoulda’s awesome macros, I setup a simple get :index which will run before each shoulda macro, then go nuts testing that @settings is assigned an array with my one and only existing setting. The page responds with success, it renders the default layout with the index template, and doesn’t set the flash. These five tests are what you should think about for every action, under every role. This is the foundation, and you add stuff from here as you add more complicated functionality to the basic scaffold.

New

  context "getting new" do
    setup do
      get :new
    end
      
    should_assign_to :setting, :class =&gt; Setting
    should_respond_with :success
    should_render_with_layout
    should_render_template :new
    should_not_set_the_flash
  end

This is very similar to index above. Now, I’m testing that @setting (singular) is set, with a single object of class Setting. This is as specific as I can test right now, because this was a newly created object in the action – so I can’t test that a specific setting was called. The rest is nearly identical to the index action, expect the template I expect to be rendered.

Create

  context "posting create" do
    context "with valid data" do
      setup do
        post :create, :setting =&gt; @valid.merge('name' =&gt; 'Slappy')
      end
        
      should_assign_to :setting, :class =&gt; Setting
      should_redirect_to("index page"){settings_path}
      should_set_the_flash_to "Your setting was successfully saved."

      should "create the record" do
        assert Setting.find_by_name('Slappy')
      end
    end
      
    context "without valid data" do
      setup do
        post :create, :setting =&gt; {}
      end
        
      should_assign_to :setting, :class =&gt; Setting
      should_respond_with :success
      should_render_with_layout
      should_render_template :new
      should_not_set_the_flash
    end
  end

Now we’re getting a little more complex. We have two outcomes to think about: a successful create, and a failed one. We have to react differently, so we have to test for both.

The first sub-context tests for success. We’re passing in a valid hash of attributes. Notice our tests look different this time – and easier on the fingers. Instead of 5 basic tests, we have 3. We still check that a Setting object was created/assigned, but instead of checking response codes and rendered content, we’re expecting the action to redirect to the index page. And this time, we are expecting a flash message to be set. Finally, I’m checking that the record was actually created. I forced the name attribute to be ‘Slappy’ because no matter how my test suite changes in the future, that name is unlikely to already be in the database.

The next sub-context tests for failure – most likely from invalid input on the part of the user. It happens. We’ve passed an empty attribute list to the action, which should ensure failure if we have at least one required attribute with validates_presence_of in the model. In my next article I’ll cover mocks and stubs for a cleaner way. Our tests are back to the familiar – a setting object should be assigned, and we should expect an HTTP response of success. Our create wasn’t successful, but that’s not what we’re testing here. We’re testing the HTTP response, and success indicates there wasn’t an error at the HTTP level. Next, we’re rendering the new template again, for the user’s second try. We’re not setting the flash though, because you’re probably going to list off the individual errors anyway.

Edit

  context "getting edit" do
    setup do
      get :edit, :id =&gt; @setting.id
    end
    
    should_assign_to(:setting){@setting}
    should_respond_with :success
    should_render_with_layout
    should_render_template :edit
    should_not_set_the_flash
  end

This is back to the short-and-sweet familiar. This is almost like our new action, with a couple exceptions. First, we can now specify (by passing a block to the should_assign_to macro) the specific setting we’re expecting, because we specified it in our request. Also, we’re expecting the edit template to be rendered.

Update

  context "putting update" do
    context "with valid data" do
      setup do
        put :update, :id =&gt; @setting.id, :setting =&gt; {:name =&gt; 'Slappy'}
      end
      
      should_assign_to(:setting){@setting}
      should_redirect_to("index page"){settings_path}
      should_set_the_flash_to 'Your knowledge base was successfully updated.'

      should "update the record"
        assert Setting.find_by_name('Slappy')
      end
    end
    
    context "with invalid data" do
      setup do
        put :update, :id =&gt; @setting.id, :setting =&gt; {:name =&gt; ""}
      end
      
      should_assign_to(:setting){@setting}
      should_respond_with :success
      should_render_with_layout
      should_render_template :edit
      should_not_set_the_flash
    end
  end

You can see we’re doing the same things here that we did with our create action. When calling update however, we can pass only the attributes we want to change. I check that it *has* changed, too.

We have to pass a hash with a piece of bad data to get a failure, and in this case just assume that name is an attribute that can’t be blank.

Destroy

  context "deleting" do
    setup do
      delete :destroy, :id =&gt; @setting.id
    end
    
    should_assign_to(:setting){@setting}
    should_redirect_to("index page"){settings_path}
    should_set_the_flash_to "Your knowledge base was successfully deleted."
    
    should "delete the record" do
      assert !Setting.find_by_id(@setting.id)
    end

This is pretty self-explanatory. We’re assigning, redirecting, and spitting out a nice flash message. We’re also doing a hard check to make sure the setting object is gone.

Members

From here, the tests get a lot easier to absorb. I’ll list the whole member context here:

context "as a member" do
  setup do
    login_as :quentin
  end
  
  context "attempting to get index" do
    setup do
      get :index
    end
    
    should_not_assign_to :settings
    should_respond_with 401
    should_render_with_layout false
    should_not_set_the_flash
  end

  context "attempting to get new" do
    setup do
      get :new
    end
    
    should_not_assign_to :setting
    should_respond_with 401
    should_render_with_layout false
    should_not_set_the_flash
  end

  context "attempting to create" do
    setup do
      post :create, :setting =&gt; {}
    end
    
    should_not_assign_to :setting
    should_respond_with 401
    should_render_with_layout false
    should_not_set_the_flash
  end

  context "attempting to get edit" do
    setup do
      get :edit, :id =&gt; 1
    end
    
    should_not_assign_to :setting
    should_respond_with 401
    should_render_with_layout false
    should_not_set_the_flash
  end

  context "attempting to update" do
    setup do
      put :update, :id =&gt; 1, :setting =&gt; {}
    end
    
    should_not_assign_to :setting
    should_respond_with 401
    should_render_with_layout false
    should_not_set_the_flash
  end

  context "attempting to delete" do
    setup do
      delete :destroy, :id =&gt; 1
    end
    
    should_not_assign_to :setting
    should_respond_with 401
    should_render_with_layout false
    should_not_set_the_flash
  end
end

The only setup I need is to login as a regular user, in this case the default “quentin” that is created by restful_authentication. Notice every action has basically the same four tests: nothing is assigned, a 401 (unauthorized) HTTP error is returned, no layout is rendered, and no flash is set. In my action calls, I can use fake id numbers and empty attribute hashes, because they’ll never be checked anyway. So, no need to create an actual setting object.

By default, role_requirement shows a blank page, but I changed that to display a special 401.html file I created. This is a person who is already logged in, so we know who they are (authentication) but does not have permission to be where they are (authorization).

Visitors

context "as a visitor" do
  context "attempting to get index" do
    setup do
      get :index
    end
    
    should_not_assign_to :settings
    should_redirect_to("login"){new_session_path}
    should_set_the_flash_to "You need to login to do this."
  end

  context "attempting to get new" do
    setup do
      get :new
    end
    
    should_not_assign_to :setting
    should_redirect_to("login"){new_session_path}
    should_set_the_flash_to "You need to login to do this."
  end

  context "attempting to create" do
    setup do
      post :create, :setting =&gt; {}
    end
    
    should_not_assign_to :setting
    should_redirect_to("login"){new_session_path}
    should_set_the_flash_to "You need to login to do this."
  end

  context "attempting to get edit" do
    setup do
      get :edit, :id =&gt; 1
    end
    
    should_not_assign_to :setting
    should_redirect_to("login"){new_session_path}
    should_set_the_flash_to "You need to login to do this."
  end

  context "attempting to update" do
    setup do
      put :update, :id =&gt; 1, :setting =&gt; {}
    end
    
    should_not_assign_to :setting
    should_redirect_to("login"){new_session_path}
    should_set_the_flash_to "You need to login to do this."
  end

  context "attempting to delete" do
    setup do
      delete :destroy, :id =&gt; 1
    end
    
    should_not_assign_to :setting
    should_redirect_to("login"){new_session_path}
    should_set_the_flash_to "You need to login to do this."
  end
end

Visitors are different from members without access. We want to be nicer and assume visitors just aren’t logged in yet, if they try to access protected actions. So we simply redirect them to the login page with a nice message. Just as with members, there’s no need to create actual objects in the database, or give actual id numbers or attribute hashes, since the action will never get far enough to check.

Conclusion

Once I developed this basic set of tests, I was pleased with how consistent my controller coding became. I don’t have a generator for these, I code them by hand every time. It keeps me sharp, and doesn’t take that long when you get into the swing of it. This isn’t the final draft, however. I’ve since modified this template with mocking and stubbing to greatly improve the speed, cutting runtime to a quarter of what it was. I’ll write about that approach in my next article.

Files

Here are Pastie links to my sample controller test and controller files. My controller is mostly vanilla scaffold with all the format junk stripped out, and a couple of key protected methods at the end.

settings_controller_test.rb
settings_controller.rb

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.