I recently developed this pattern in order to clean up my tests and shoulda contexts. The initial inspiration was that I needed a global parameter to be passed all the time, for security purposes. Then I figured I could also use it with my nested contexts, to get cleaner tests. The special sauce is the overloading of get and post methods, which merges with the @params instance variable. I am also using the @action variable to hold a proc, so that I can keep my tests DRYer. Please let me know what you think in the comments!

PS also notice the use of the slick new should_change macro, which assists me in keeping to one assert per should (and not having to put assert_difference blocks in my setups). (thanks to Ryan McGeary! lighthouse ticket, github commit )


require File.dirname(__FILE__) + '/../test_helper'
require 'events_controller'
require "test/unit" 

# Re-raise errors caught by the controller.
class EventsController; def rescue_action(e) raise e end; end

class EventsControllerTest < Test::Unit::TestCase

  ## special sauce for global parameters
  [ :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
    @controller           = EventsController.new
    @request              = ActionController::TestRequest.new
    @response             = ActionController::TestResponse.new
    @event = create_event
    @user = User.first || create_user
    @params = {:security_token => 'abc123' } # add any global params you need here
  end

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

    context "with no RSVP existing in DB" do
      setup do
        if rsvp = @user.rsvps.find_by_event_id( @event.id )
          rsvp.destroy
        end
      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

    context "with an existing RSVP in DB" do
      setup do
        @rsvp = create_rsvp( :event_id => @event.id, :user_id => @user.id, :status => 'not_attending' )
      end

      context "" do    # HACK: this blank context is needed to separate the create_rsvp from the should_not_change call (without it, the count would change by 1)
        setup do
          @params[:status] = 'attending'
          @action.call
        end
        should_respond_with :success
        should_not_change "Rsvp.count" 
        should "update the status" do
          assert_equal @params[:status], @rsvp.reload.status
        end
      end      
    end

  end

  context "a POST to :create" do
    setup do
      @action = lambda{ post :create }
    end

    context "when the form fields are blank" do
      setup do
        @params[:event] = {}
      end
      should_respond_with :success
      should_render_template :edit

      should "show validation errors" do
        assert_select '.fieldWithErrors'
      end
    end

    context "with valid params and :publish => true" do
      setup do
        @params[:event] = { :title => "Some New Event", :starts_on => Date.today, :all_day => true, :ends_on => Date.today+1 }
        @params[:publish] = true
        @action.call
      end
      should "show up in event index" do
        @event = Event.find_by_title("Some New Event")
        assert Event.visible.include?( @event )
      end

      context "and no end date" do
        setup do
          @params[:ends_on] = nil
          @action.call
        end

        should_respond_with :success
        should_render_template :show

        should "show up in event index" do          
          @event = Event.find_by_title("Some New Event")
          assert Event.visible.include?( @event )
        end
      end

    end
  end
end

Sorry, comments are closed for this article.