shoulda request params macro

So in a flash of inspiration on the airplane last week, I jotted down a new version of my shoulda params pattern. Put this pastie in test/shoulda_macros/request_params.rb

The general idea is this:


a_get_to(:index) do
  with_params( {:foo => 'bar'}, "valid params" ) do
    should_respond_with :success

    with_params( :more => 'nested stuff' ) do
      should "do something extra special" do
         assert true
      end
    end

  end
end


Here’s what it looks like in action, taken from the ScrumNinja.com code:

require File.dirname(__FILE__) + '/../test_helper'

class MainControllerTest < ActionController::TestCase
  @@message = "you guys rock!" 

  context "When not logged in" do
    setup do
      login_as( nil )
    end
    %w[ index tour contact api ].each do |action|
      a_get_to( action, params=false ) do
        should_respond_with :success
      end
    end

    a_get_to( :feedback, params=false ) do
      should_redirect_to "'/login'" 
    end    

    a_post_to( :contact, :message => @@message ) do
      should_send_email :to => "Mailer::GO_EMAIL", :body => %r{#@@message}
      should_redirect_to "'/'" 
    end
  end

  context "When logged in" do
    setup do
      login_as( @user = create_user )
    end
    a_post_to( :feedback, :message => @@message ) do
      should_send_email :to => "Mailer::GO_EMAIL", :body => %r{#@@message}
      should_respond_with :redirect
    end
  end

end


Factories-and-workers is now updated to not only be a plugin, but also a gem. Thanks to Jonathan Barket and Nick Hoffman for their help!

It is also now automatically loaded for test and development environments (as a plugin, or do it manually as below with the unless statement). I find this really comes in handy for using it in the rails console to populate your database.

Get It

Add it as a gem dependency to your rails environment.rb:


  config.gem 'dfl-factories-and-workers', :lib => 'factories-and-workers', :source => 'http://gems.github.com' unless RAILS_ENV=='production'

Or install it as a gem manually:

  gem sources -a http://gems.github.com
  sudo gem install dfl-factories-and-workers

Or grab the source:

  git clone git://github.com/dfl/factories-and-workers.git

I previously posted about global params hashes for nested shoulda contexts in functional tests. This post is a followup that shows how my solution has since crystallized.

Here’s the special sauce to add to your test_helper.rb:

ActionController::TestCase.class_eval do
  # special overload methods for "global"/nested params
  [ :get, :post ].each do |overloaded_method|
    define_method overloaded_method do |*args|
      action,params,extras = *args
      super action, @params.merge( params || {} ), *extras
    end
  end

  def setup
    super
    @params = {}
  end
end

This creates a @params variable that gets merged with all HTTP requests. Now you can use global params in your nested contexts as such:

class FooControllerTest < ActionController::TestCase
  def setup
    super
    @params[:security_token] = 'abc123' # add any global params you need here
    @event = create_event    
  end

  context "A POST to :action" do
    setup do
      @action = lambda{ post :action, :id => @event.id }
    end

    %w[ attending not_attending maybe_attending ].each do |status|
      context "with :status = '#{status}'" do
        setup do
          @params[:status] = status
          @action.call
        end          
        should_respond_with :success
        should_change "Rsvp.count", :by => 1
        should "create the proper Rsvp object" do
          assert Rsvp.find_by_user_id_and_event_id_and_status( @user.id, @event.id, status )
        end
      end
    end
  end

end

I just cooked up a macro where I needed to access an instance variable from the setup blocks. However the scope of should statements are at the class level, so we don’t have access to instance variables from there (only class variables). I found the solution inside the code for should_redirect_to, which evals the object in the binding of the should block.


class ProjectsControllerTest < ActionController::TestCase
  def setup
    super
    @project = create_project
  end

  %w[ client developer ].each do |role|
    context "A #{role} user" do
      setup do
        login_as( user = create_user )
        @project.create_role_for!( user, role )
      end

      should_not_allow :destroy, "@project" 
      should_not_allow :edit, "@project"    
    end
  end
end
This next file goes in test/shoulda_macros/*.rb, which will be auto-loaded:

Test::Unit::TestCase.class_eval do

  def self.should_not_allow action, object, msg=nil
    msg ||= "a #{object.class.to_s.downcase}" 
    should "not be able to #{action} #{msg}" do
      object = eval(object, self.send(:binding), __FILE__, __LINE__)
      get action, :id => object.id
      assert_response 404
    end
  end

  def self.should_allow action, object, msg=nil
    msg ||= "a #{object.class.to_s.downcase}" 
    should "be able to #{action} #{msg}" do
      object = eval(object, self.send(:binding), __FILE__, __LINE__)
      get action, :id => object.id
      assert_response :success
    end
  end

end

I’ve been working more on factories-and-workers

Yesterday I came up with the idea to make a rake task to ease the initial entry into factory-land. It grabs info from the schema, via the model.columns hash, and prints out a factory template, to be pasted and edited in your factories.rb file.

Check it out:



dfl% rake factory:generate MODEL=task_estimate

factory :task_estimate, {
  :task => :belongs_to_model,
  :date => lambda{ Time.zone.today - 7 },
  :hours => 1.23,
}

If called without the MODEL argument, it will print factory templates for all models.

factories-and-workers at github

Factories and Workers is a Rails plugin originally written by Nathan Herald @ myobie.com after being inspired by Dan Manges’ blog post on factories. It uses some slick metaprogramming to generate factory methods for your ActiveRecord models. Over the past few months I’ve refactored the code, added a bunch of new features, and most importantly wrote some tests!

See it in action:

>> factory :monkey, { :name => "George" }
=> #<FactoryBuilder:0x1a4f2a8>

>> valid_monkey_attributes
=> {:name=>"George"}

>> build_monkey
=> #<Monkey id: nil, name: "George">

>> build_monkey( :name => "Bob" )
=> #<Monkey id: nil, name: "Bob">

>> Monkey.count
=> 0

>> create_monkey
=> #<Monkey id: 1, name: "George">

>> Monkey.count
=> 1

There’s much more info in the README.

I’ve been playing with block helpers for some time now, and I love them for their semantic goodness and clarity. I know that Rails 2 has an assert_email method, which plays nicely with assert_select assert_select_email method. However, as far as I could tell, this doesn’t allow you to assert any of the headers such as to, from, etc.

Enter assert_emails_sent:


  def test_send_crash_data
    device_id = 2
    assert_email_sent :to => AdminNotifier::CLIENT_CRASH_EMAIL,
                      :from => AdminNotifier::SYSTEM_EMAIL,
                      :subject => "Application crashed.",
                      :body => /device id #{device_id} crashed.+whoops I crashed/ do
      AdminNotifier.deliver_send_crash_data('whoops I crashed', device_id)                      
    end
  end

  • One cool feature is that the values of :to and :from will be automagically sent #email, so you can just say :to => @user, as long as @user.responds_to?(:email).
  • Another bonus feature is that you can use either strings or regexps.

grab the code here.

staying DRY with custom shoulds

Recently I’ve been refactoring legacy Test::Unit code to use shoulda. Sometimes you can’t factor out repetitive stuff into a context, but you still want to be DRY. Below is an example case of refactoring into a custom should. Note that you need to use class variables, and that the self.should_* method needs to be defined before it’s actually used, since shoulda is built upon metaprogramming which gets evaluated at the class level.


...

  def self.should_request sym
    sym = sym.to_s
    context "when requesting #{sym}" do
      setup do
        get :download, {:id => @@podcast.id, :type => sym}, @@session
      end
      should "ask for a device" do
         assert_match /Please select a device to download content/,  @response.body
      end
      should "increment the message count when sent #add_to_device" do
        assert_difference 'Message.count( :conditions => "device_id = 1")', @@podcast.episodes.size do
          xhr :post, :add_to_device, {:id => 1, :type => 'all', :podcast_id => @@podcast.id}, @@session
        end
      end
    end
  end

  context "A valid account" do
    setup do
      @account = accounts(:accounts_002)
      @@session = {:account_id => @account.id}
      @account.devices << Device.find(1)
      @@podcast = podcasts(:joe_cartoon)
      @@session.merge!({:podcast_episodes => @@podcast.episodes})
    end

    should_request :all
    should_request :latest
    should_request :episode
  end

...

I was suffering with a problem running tests inside of textmate on a Rails 2 project, until I found this comment by David Vrensk at the bottom of this blog post by Rob Sanheim:

Posted by David Vrensk 18 January 2008 @ 9am

I think the easiest solution can be gleaned from the TM ticket that you link to (http://macromates.com/ticket/show?ticket_id=F4DA8B03). I just modify test/test_helper.rb in my current projects so that it starts with

$:.reject! { |e| e.include? ‘TextMate’ }

No patching the distros, and svn still works the way it should.

valid model attribute factory test helpers

I like to use factory test helpers to generate attributes which I then pass to ActiveRecord. The default output is expected to be valid (hence the usage with create-bang below). If you want to override any of the defaults, you can simply use Hash#merge.

Here’s an example, using shoulda. It should be pretty clear how it can be applied to vanilla Test::Unit (or the testing framework du jour ;).


##  I put this in test_helper.rb so I can use it across all tests.
## You could put it in the actual derived test class as well.

  def valid_user_attrs( unique = DateTime.now )
    { :email => "{unique.hash.abs}@example.com", :first_name => "dummy", :last_name => "user_#{unique}", :profile_attributes => valid_profile_attrs }
  end

  def valid_profile_attrs
    ...
  end


## Here’s the actual test case
  
class UserTest < Test::Rails::TestCase

  context "Two Users" do
    setup do
      @user = User.create!( valid_user_attrs )
      @user2 = User.create!( valid_user_attrs.merge( :email => "dummy123@dummy.com" ) )
    end

    should "not be friends" do
      assert !@user.friends.include?( @user2 )
      assert !@user2.friends.include?( @user )
    end

  end
end

		
		
	

friendly_fixtures plugin for Rails

In a previous post, I posted a monkey patch to enhance the behavior of fixtures. I converted it into a plugin format for convenience and testing purposes. I am still aiming to submit it as a patch to Rails 2.0. I’d appreciate any feedback!

get it here:

svn export --username=public svn://internautdesign.com/public/plugins/friendly_fixtures vendor/plugins/friendly_fixtures

UPDATE: now available at github: http://github.com/dfl/friendly_fixtures/tree/master

from the README:

FriendlyFixtures

This plugin is a simple extension to add some cool features to the fixtures macro in Test::Unit. It is intended for Rails 2.0, but works with 1.2.3 as well.

It enables you to:
  • load dependent models, which are found by object introspection on a model’s ActiveRecord associations.
  • assert that all the loaded fixtures are valid. This can be very helpful in finding bugs.

Example Usage:


class SomeTest < Test::Unit::TestCase
  fixtures :user, :dependencies => true, :validate => true
end

I recently wrote a functional test for an AJAX callback action. I wanted to assert that the RJS replaced some text with a particular string. This string is generated by a view helper. To keep this flexibility, I wanted to call the helper from the test.

It turns out to be quite simple. All you have to do is mix-in the helper to your TestCase class, like so:

class CompanyControllerTest < Test::Rails::TestCase
  include CompanyHelper
...
end

this replaces my earlier post about testing for valid fixtures. Rails 2.0 patch coming soon…?

UPDATE: This monkey patch has been replaced by a plugin (with tests!): friendly_fixtures.

Rathole, a fixtures plugin so sweet it’s been incorporated into edge rails, is great—but unfortunately it doesn’t support polymorphic associations. That is, until now! :)

Get the patch: here.

UPDATE: I learned that the author, John Burnette, has renamed the project to foxy fixtures. He also massaged my patch into a contribution for rails 2.0: Ticket 10183.

There is some discussion that this patch is not necessary.

textmate bundle for shoulda

I’m pleased to announce a textmate bundle that I assembled for the wonderfully simple shoulda testing framework.

The snippets were contributed by Dan Croak, James Golick, and Sam Livingston-Gray, and mashed up by me.

Grab the latest version here: http://macromates.com/svn/Bundles/trunk/Review/Bundles/Ruby%20Shoulda.tmbundle

a yaml_to_shoulda rake task

Inspired by err’s cryptic yaml_to_spec rake task, I wrote my own version for shoulda, based off of Jeremy Hubert’s textmate bundle It’s a bit less cryptic, and it for extra nerd points it uses recursion to allow for nested contexts. :)


desc "Converts a YAML file into a Shoulda skeleton" 
task :yaml_to_shoulda do
  require 'yaml'

  def yaml_to_context hash, indent=0
    indent1 = '  '*indent
    indent2 = '  '*(indent+1)
    hash.each_pair do |context,shoulds|
      puts indent1+"context \"#{context}\" do" 
      puts    
      shoulds.each do |should|
        yaml_to_context( should, indent+1 ) and next if should.is_a?( Hash )
        puts indent2+"should_eventually \"#{should.gsub(/^should +/,'')}\" do" 
        puts indent2+"end" 
        puts
      end
    puts indent1+"end" 
  end 

  yaml_to_context( YAML.load_file( ENV['FILE'] || !puts("Pass in FILE argument.") && exit ) )
end

Here is an example YAML file and it’s output:

This blog post:
- should mention shoulda
- should be concise
- should be written by me
- when saved as a draft:
  - should have multiple revisions
  - should not be published publicly



context "This blog post" do

  should_eventually "mention shoulda" do
  end

  should_eventually "be concise" do
  end

  should_eventually "be written by me" do
  end

  context "when saved as a draft" do

    should_eventually "have multiple revisions" do
    end

    should_eventually "not be published publicly" do
    end

  end
end

I will be adding this to my shoulda textmate bundle.

shoulda: a spec framework without the hype

This morning I just discovered Shoulda, a really sweet testing framework that gives context and behavior without the whizz-bang syntax of RSpec and test-spec. Check it out!

My only complaints so far:

  • the example code on their homepage is missing “do” keywords after some of the “should” statements. oops!
  • I can no longer run an individual test in textmate with APPLE-shift-R, as there is no literal method to run, just a block. Hopefully this will be fixed with a forthcoming shoulda textmate bundle.

assert_incremented_by test helpers

I wrote these test_helper methods after being inspired by Zack Chandler’s assert_toggled method


  def assert_incremented_by( number, object, method, *args )
    initial_value = object.send(method, *args)
    yield
    object.reload if object.respond_to? :reload
    assert_equal initial_value + number, object.send(method, *args)
  end  

  def assert_decremented_by( number, object, method, *args, &block )
    assert_incremented_by -number, object, method, *args, &block
  end  

  def assert_no_change_in( object, method, *args )
    initial_value = object.send(method, *args)
    yield
    object.reload if object.respond_to? :reload
    assert_equal initial_value, object.send(method, *args)
  end  


example usage:

  def test_ajax_create_network__for_city_is_successful_and_updates_form_id_value
    assert_incremented_by 1, City, :count do
      xhr :post, :ajax_create_network, :type => 'City', :object => { :name => 'Dummy', :state => 'CA' }
    end
    assert_response :success
    assert_match /\.value='\d+'/, @response.body
  end

...

  def test_after_create__watchlist_notification_on_posting
    assert users(:david).watchlist_companies.include?( companies(:HP) )
    assert_incremented_by 1, WatchlistNotification, :count, :conditions => {:kind => 'posting_commented'} do
      postings(:rodney_HP).comments.create!( :body => 'booyashaka!', :user => users(:greg) )
    end  
  end

  def test_after_create__watchlist_notification__doesnt_happen_on_profile
    assert users(:david).watchlist_companies.include?( companies(:HP) )
    assert_no_change_in WatchlistNotification, :count do
      users(:rodney).profile.comments.create!( :body => 'booyashaka!', :user => users(:greg) )
    end  
  end


assert fixtures are valid

put this in your test_helper.rb


  def test_all_models_valid model
    objs = model.camelize.constantize.find(:all)
    objs.each do |obj|
      assert obj.valid?, "#{model} #{obj.id} is invalid: #{obj.errors.full_messages.join(', ')}" 
    end
  end

  def test_all_models_valid! filename
    test_all_models_valid klass = filename.split('/').last.sub(/_test.rb/,'').camelize
  end

Now you can create a test like this in each of your your model/unit tests:

  def test_valid_fixtures
    test_all_models_valid! __FILE__
  end

Sweet! :)

Update #1: Thanks to Nicolas Sanguinetti for the tip on how to avoid the use of eval.

actionmailer ActionView ActiveRecord activesupport agile ajax alphadecimal audio autotest BDD blocks capistrano ssh ruby console controller css dashboard widget delegate dog puppy naming name DRM email obfuscation exceptions factories factories-and-workers factory pattern filemerge find and replace finder fink fixtures fun functional testing gem gems git google maps haml hash helper helpers imagemagick install Intertrust irb javascript jquery jrails logo macro math meetup model openssl OS X patch Pioneer Electronics plugin polymorphism project management prototype.js rails rails gotcha rails,patch,validations railsconf rake rmagick RSA encryption ruby ruby on rails script scrum scrum lessons rails scrumninja shoulda subversion SyncTV TDD testing textmate tips tricks unique hashes unix shell validation view yaml zebra stripes