Posted by Jim Morris
Sat, 28 Jul 2007 21:23:07 GMT
A pattern I find very helpful is to find all the actions in a
controller and apply a test to all those actions.
For instance this is useful for automatically testing all actions are
protected from unauthorized access when using a login system.
One nice feature of this pattern is that if you add an action to a
controller it will automatically be tested. This is less helpful if
you use
before_filter :login_required, :except => {...}
as it will automatically be protected, but there are other use cases
where this is not the situation. Just as in the except clause above
you need to explicitly add any action that does not need to be tested
to an exception list, which is supported by this pattern.
Here are the methods I use to test for login accessibility.
module MySpecHelper
def get_all_actions(cont)
c= Module.const_get(cont.to_s.pluralize.capitalize + "Controller")
c.public_instance_methods(false).reject{ |action| ['rescue_action'].include?(action) }
end
def controller_actions_should_fail_if_not_logged_in(cont, opts={})
except= opts[:except] || []
actions_to_test= get_all_actions(cont).reject{ |a| except.include?(a) }
actions_to_test += opts[:include] if opts[:include]
actions_to_test.each do |a|
get a
response.should_not be_success
response.should redirect_to('http://test.host/login')
flash[:warning].should == @login_warning
end
end
end
I put this in my spec_helper.rb and include it as shown here:
describe "When Logged out" do
include MySpecHelper
controller_name :events
before(:each) do
controller.stub!(:current_user).and_return(:false)
@login_warning= "You need to be logged in to do that"
end
it "actions should fail" do
controller_actions_should_fail_if_not_logged_in(:input,
:except => ['index', 'show', 'tagged'],
:include => ['new_comment'])
end
end
The get_all_actions method collects all the public un-inherited
methods in the given controller, these will consist of all the
accessible actions in that controller. I explicitly exclude
rescue_action as it is created by RSpec itself and should not be
tested. Note it will not see any actions that are in application.rb so
you need to add those to the list manually of you want them tested.
(See the :include option in the example).
The controller_actions_should_fail_if_not_logged_in could be put in
the spec itself rather than the spec_helper, but as I call this from
all my controller specs it is more DRY to put it here. This method
takes the controller name and an option array of actions names to
ignore. This method tests all the actions and makes sure I get the
expected result of the filter failing due to not being logged in.
I show an example spec that uses this to test my events controller, it
mocks the login calls to say I am not logged in, and then tests them
with the exceptions of the actions in this controller that do not
require one to be logged in.
This pattern can be extended to test all sorts of things, and is
especially useful for testing things where you can add an action and
forget to do something in a filter to protect it. Make sure the
default is on the side of caution though. IE you need to explicitly
except actions rather than include actions.
Another example is something I recently stumbled upon in my RESTful
controllers. In many cases it is good to use a verify statement to
make sure that the RESTful actions actually can only be called with
PUT, POST or DELETE and fail if called with GET. I use this statement
in my controllers to enforce this...
verify :method => :put, :only => [ :update ], :add_flash => { :error => "Operation Failed" }, :redirect_to => { :action => :index }
verify :method => :post, :only => [ :create, :new_comment ], :add_flash => { :error => "Operation Failed" }, :redirect_to => { :action => :index }
verify :method => :delete, :only => [ :destroy ], :add_flash => { :error => "Operation Failed" }, :redirect_to => { :action => :index }
I test this in my specs using this in the MySpecHelper Module
def controller_actions_should_fail_with_get(cont, except=[])
actions_to_test= get_all_actions(cont).reject{ |a| except.include?(a) }
actions_to_test.each do |a|
get a
response.should redirect_to("http://test.host/#{cont.to_s.pluralize}")
flash[:error].should == 'Operation Failed'
end
end
and an example of its use in a spec...
it "actions should fail if not post or put" do
controller_actions_should_fail_with_get(:event, ['index', 'show', 'edit', 'new'])
end
Now whenever I add an action, the default is that it will fail with a
GET, unless I add it to the exclude list in the spec, this will remind
me to check if the action required PUT, POST or DELETE instead and to
add it to the verify if so or add it to the specs exclude list if not.
These automatic tests keep me honest, especially in the last case
where you really don't want a GET to be able to delete something.
I hope this pattern is useful to you.
Posted in RSpec, Rails | Tags controllers, rails, rspec | 12 comments | no trackbacks
Posted by Jim Morris
Sat, 28 Jul 2007 21:23:07 GMT
A pattern I find very helpful is to find all the actions in a
controller and apply a test to all those actions.
For instance this is useful for automatically testing all actions are
protected from unauthorized access when using a login system.
One nice feature of this pattern is that if you add an action to a
controller it will automatically be tested. This is less helpful if
you use
before_filter :login_required, :except => {...}
as it will automatically be protected, but there are other use cases
where this is not the situation. Just as in the except clause above
you need to explicitly add any action that does not need to be tested
to an exception list, which is supported by this pattern.
Here are the methods I use to test for login accessibility.
module MySpecHelper
def get_all_actions(cont)
c= Module.const_get(cont.to_s.pluralize.capitalize + "Controller")
c.public_instance_methods(false).reject{ |action| ['rescue_action'].include?(action) }
end
def controller_actions_should_fail_if_not_logged_in(cont, opts={})
except= opts[:except] || []
actions_to_test= get_all_actions(cont).reject{ |a| except.include?(a) }
actions_to_test += opts[:include] if opts[:include]
actions_to_test.each do |a|
get a
response.should_not be_success
response.should redirect_to('http://test.host/login')
flash[:warning].should == @login_warning
end
end
end
I put this in my spec_helper.rb and include it as shown here:
describe "When Logged out" do
include MySpecHelper
controller_name :events
before(:each) do
controller.stub!(:current_user).and_return(:false)
@login_warning= "You need to be logged in to do that"
end
it "actions should fail" do
controller_actions_should_fail_if_not_logged_in(:input,
:except => ['index', 'show', 'tagged'],
:include => ['new_comment'])
end
end
The get_all_actions method collects all the public un-inherited
methods in the given controller, these will consist of all the
accessible actions in that controller. I explicitly exclude
rescue_action as it is created by RSpec itself and should not be
tested. Note it will not see any actions that are in application.rb so
you need to add those to the list manually of you want them tested.
(See the :include option in the example).
The controller_actions_should_fail_if_not_logged_in could be put in
the spec itself rather than the spec_helper, but as I call this from
all my controller specs it is more DRY to put it here. This method
takes the controller name and an option array of actions names to
ignore. This method tests all the actions and makes sure I get the
expected result of the filter failing due to not being logged in.
I show an example spec that uses this to test my events controller, it
mocks the login calls to say I am not logged in, and then tests them
with the exceptions of the actions in this controller that do not
require one to be logged in.
This pattern can be extended to test all sorts of things, and is
especially useful for testing things where you can add an action and
forget to do something in a filter to protect it. Make sure the
default is on the side of caution though. IE you need to explicitly
except actions rather than include actions.
Another example is something I recently stumbled upon in my RESTful
controllers. In many cases it is good to use a verify statement to
make sure that the RESTful actions actually can only be called with
PUT, POST or DELETE and fail if called with GET. I use this statement
in my controllers to enforce this...
verify :method => :put, :only => [ :update ], :add_flash => { :error => "Operation Failed" }, :redirect_to => { :action => :index }
verify :method => :post, :only => [ :create, :new_comment ], :add_flash => { :error => "Operation Failed" }, :redirect_to => { :action => :index }
verify :method => :delete, :only => [ :destroy ], :add_flash => { :error => "Operation Failed" }, :redirect_to => { :action => :index }
I test this in my specs using this in the MySpecHelper Module
def controller_actions_should_fail_with_get(cont, except=[])
actions_to_test= get_all_actions(cont).reject{ |a| except.include?(a) }
actions_to_test.each do |a|
get a
response.should redirect_to("http://test.host/#{cont.to_s.pluralize}")
flash[:error].should == 'Operation Failed'
end
end
and an example of its use in a spec...
it "actions should fail if not post or put" do
controller_actions_should_fail_with_get(:event, ['index', 'show', 'edit', 'new'])
end
Now whenever I add an action, the default is that it will fail with a
GET, unless I add it to the exclude list in the spec, this will remind
me to check if the action required PUT, POST or DELETE instead and to
add it to the verify if so or add it to the specs exclude list if not.
These automatic tests keep me honest, especially in the last case
where you really don't want a GET to be able to delete something.
I hope this pattern is useful to you.
Posted in RSpec, Rails | Tags controllers, rails, rspec | 12 comments | no trackbacks
Posted by Jim Morris
Sat, 14 Jul 2007 23:40:00 GMT
UPDATED for HAML 2.0 and RSpec 1.1.5 - Changed open to haml_tag, prefix helper. to all rspec calls...
The most recent release of HAML
introduced a neat feature that allows you to use HAML-like syntax in
your helpers to generate HTML
HAML#haml_tag.
A question on the HAML news group asked how to test a helper that uses
HAML#haml_tag (used to be open/puts) and thanks to Nathan on that
list
I finally got RSpec to do it. As shown below.
However a really good point was made that really in RSpec the way to
test anything is to use mocks to mock any call to an outside method
thus focusing the test on the specific module under test.
Generally I agree with that philosophy. But this is way cool so I
thought I'd do it anyway, and also as it is a new feature in HAML one
may not want to simply trust HAML to generate the correct HTML.
So in my application_helper.rb I have a simple helper...
module ApplicationHelper
...
def display_flash
for name in [:notice, :warning, :error]
if flash[name]
haml_tag :div, flash[name], {:class => name.to_s}
end
end
nil
end
...
end
This is called in my views as...
- display_flash
Notice the - instead of =, this is because the open (and puts) write
output directly to the HAML buffer, and so this routine should return
nothing. (This is also a very simply case and does not show off the
utility of the open/puts methods, I'll show one of those later on).
The RSpec helper test that tests this is as follows...
require File.dirname(__FILE__) + '/../spec_helper'
describe ApplicationHelper do
before :each do
helper.extend Haml
helper.extend Haml::Helpers
helper.send :init_haml_helpers
end
it "should display flash" do
for name in [:notice, :warning, :error]
flash[name]= "flash #{name.to_s} message"
helper.capture_haml{
helper.display_flash
}.should =~ /<div class='#{name.to_s}'>\s*#{flash[name]}\s*<\/div>/
flash[name]= nil
end
end
end
Excellent, a simple test for the HTML generated by my haml helper.
NOTE the setup required in the before :each, this sets up the haml helpers in the helpers context
Why would I want to use HAML#open you ask?
Well it makes the helpers look so much tidier IMHO, take this example
from my previous post on
tag clouds,
the re-factored helper now looks like this...
def tag_cloud(model, title= nil)
m= model.to_s.camelcase.constantize
plural= model.to_s.capitalize.pluralize
title ||= plural
tags= m.tag_counts(:order => 'tags.name')
return false if tags.empty?
urlmeth= "tagged_#{model.to_s.pluralize}_path".to_sym
haml_tag :div, {:class => "tagcloud"} do
haml_tag :h3, title
tags.each do |t|
next if t.name == 'FAQ'
haml_tag :span, {:style => "font-size:#{calc_size(t.count)}%"} do
puts link_to(h(t.name), self.send(urlmeth, :tag => t.name))
end
end
end
return true
end
So much cleaner, plus I can return a boolean to indicate if there was
anything output or not, which tells me if I need to output an <hr/> or
not.
Posted in RSpec, Rails, HAML | Tags haml, helpers, rspec, test | 8 comments | no trackbacks
Posted by Jim Morris
Sat, 14 Jul 2007 23:40:00 GMT
UPDATED for HAML 2.0 and RSpec 1.1.5 - Changed open to haml_tag, prefix helper. to all rspec calls...
The most recent release of HAML
introduced a neat feature that allows you to use HAML-like syntax in
your helpers to generate HTML
HAML#haml_tag.
A question on the HAML news group asked how to test a helper that uses
HAML#haml_tag (used to be open/puts) and thanks to Nathan on that
list
I finally got RSpec to do it. As shown below.
However a really good point was made that really in RSpec the way to
test anything is to use mocks to mock any call to an outside method
thus focusing the test on the specific module under test.
Generally I agree with that philosophy. But this is way cool so I
thought I'd do it anyway, and also as it is a new feature in HAML one
may not want to simply trust HAML to generate the correct HTML.
So in my application_helper.rb I have a simple helper...
module ApplicationHelper
...
def display_flash
for name in [:notice, :warning, :error]
if flash[name]
haml_tag :div, flash[name], {:class => name.to_s}
end
end
nil
end
...
end
This is called in my views as...
- display_flash
Notice the - instead of =, this is because the open (and puts) write
output directly to the HAML buffer, and so this routine should return
nothing. (This is also a very simply case and does not show off the
utility of the open/puts methods, I'll show one of those later on).
The RSpec helper test that tests this is as follows...
require File.dirname(__FILE__) + '/../spec_helper'
describe ApplicationHelper do
before :each do
helper.extend Haml
helper.extend Haml::Helpers
helper.send :init_haml_helpers
end
it "should display flash" do
for name in [:notice, :warning, :error]
flash[name]= "flash #{name.to_s} message"
helper.capture_haml{
helper.display_flash
}.should =~ /<div class='#{name.to_s}'>\s*#{flash[name]}\s*<\/div>/
flash[name]= nil
end
end
end
Excellent, a simple test for the HTML generated by my haml helper.
NOTE the setup required in the before :each, this sets up the haml helpers in the helpers context
Why would I want to use HAML#open you ask?
Well it makes the helpers look so much tidier IMHO, take this example
from my previous post on
tag clouds,
the re-factored helper now looks like this...
def tag_cloud(model, title= nil)
m= model.to_s.camelcase.constantize
plural= model.to_s.capitalize.pluralize
title ||= plural
tags= m.tag_counts(:order => 'tags.name')
return false if tags.empty?
urlmeth= "tagged_#{model.to_s.pluralize}_path".to_sym
haml_tag :div, {:class => "tagcloud"} do
haml_tag :h3, title
tags.each do |t|
next if t.name == 'FAQ'
haml_tag :span, {:style => "font-size:#{calc_size(t.count)}%"} do
puts link_to(h(t.name), self.send(urlmeth, :tag => t.name))
end
end
end
return true
end
So much cleaner, plus I can return a boolean to indicate if there was
anything output or not, which tells me if I need to output an <hr/> or
not.
Posted in RSpec, Rails, HAML | Tags haml, helpers, rspec, test | 8 comments | no trackbacks