Perfecting an RSpec macro for controllers
I'm trying to generate an easy macro for a Rails app that uses Devise for authentication. Basically I want to ensure that when a user accesses a page that requires authentication, they're redirected to the login page. So something like this:
it_requires_authentication_for :index, :new, :create, :update
The desired results here should be obvious. My problem however is that I can't think of the best way to map each action to its appropriate http method (:get, :post etc...)
I started out with this:
def it_should_require_authentication_for(*actions)
actions.each do |action|
it "should require authentication for #{action}" do
get action.to_sym
response.should redirect_to( new_user_session_path )
end
end
end
Which of course only does the get. Can someone tell me how I might provide this macro for all actions? I'm assuming I need to somehow test if the action routes properly for a particular method, but I'm just not really sure.
Any help is greatly apprecia开发者_如何学Goted.
maybe old, but still can help some.
Here is a nice and easy way to define macros in RSpec (even for controllers).
http://osmose.6spot.com.br/2011/02/better-macros-with-rspec/
Look, using method missing you are able to record specific behavior into your recorded macros, for instance, this is a scaffold controller spec with a specific stub instruction:
describe CustomersController do before(:each) do mock_filter(:require_user_owner) end # GET /customers get :index do default :stub => :off before(:each) do Customer.stub(:find_all_by_user_id) { [mock_customer] } end end # GET /customers/6 get :show, :id => 6 # GET /customers/new get :new # GET /customers/6/edit get :edit, :id => 6 # POST /customers post :create # PUT /customers/6 put :update, :id => 6 # DELETE /customers/6 delete :destroy, :id => 6 end
I'm using the following until I come up with something more elegant:
# controller_macros.rb
def it_should_recognize_and_generate_routes_for(controller, routes)
describe "routing" do
routes.each do |route|
action = route[:action].to_s
method = route[:method] || :get
url = controller + (route[:url] || '')
params = route.reject {|k, v| [:action, :method, :url].include?(k) }
expected = { :controller => controller, :action => action }.merge(params)
it "should recognize and generate '#{action}'" do
{ method => url }.should route_to(expected)
end
end
end
end
# posts_controller_spec.rb
describe Forum::PostsController do
it_should_recognize_and_generate_routes_for('forum/posts', [
{ :action => :new, :url => '/new' },
{ :action => :create, :method => :post },
{ :action => :show, :url => '/1', :id => '1' },
{ :action => :index },
{ :action => :edit, :url => '/1/edit', :id => '1' },
{ :action => :update, :method => :put, :url => '/1', :id => '1' },
{ :action => :destroy, :method => :delete, :url => '/1', :id => '1' }
])
end
BTW I still have to extend it to work with routes like:
get 'login' => 'user_sessions#new'
This Railscast covers exactly what you are looking for: http://railscasts.com/episodes/157-rspec-matchers-macros
It looks like you can just call the get method with your desired action in your controller spec, and it will call the appropriate method in the controller.
Here is my version of the macro:
# Last argument is an optional hash of http arguments
# which is useful when working with nested models
#
# ex it_should_require_login_for_actions(:index,:new,:create, {:parent_id=>1})
#
def it_should_require_login_for_actions(*actions)
request_args = {:id => 1}
#If the last element of the actions list is a hash, then merge it
# with the existing request arguments
if actions[-1].is_a?(Hash)
request_args.merge!(actions.pop())
end
actions.each do |action|
it "#{action} action should require login" do
get action, request_args
response.should redirect_to(login_url)
end
end
end
精彩评论