Skip to content

Instantly share code, notes, and snippets.

@rjharmon
Created June 17, 2009 22:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rjharmon/131569 to your computer and use it in GitHub Desktop.
Save rjharmon/131569 to your computer and use it in GitHub Desktop.
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
# NOTE: Existing routing specs will continue passing 100%, so nobody has to change
# anything they have already written for rspec 1.2 compat.
# The current routing spec scaffold can be shortened to remove the route recognition specs.
# ahem. After originally posting, I realized that I hadn't tested .should get() the way I thought I had.
# those aren't working. So, we can punt on that part, or maybe figure out a way to get it.
describe SitesController do
it "maps #index" do
route_for(:controller => "sites", :action => "index").should == "/sites"
end
it "maps #new" do
# same as .should ==
route_for(:controller => "sites", :action => "new").should get("/sites/new")
end
it "maps #show" do
route_for(:controller => "sites", :action => "show", :id => "1").should get("/sites/1")
end
it "maps #edit" do
route_for(:controller => "sites", :action => "edit", :id => "1").should get("/sites/1/edit")
end
it "maps #create" do
route_for(:controller => "sites", :action => "create").should post("/sites")
end
it "maps #update" do
route_for(:controller => "sites", :action => "update", :id => "1").should put("/sites/1")
end
it "maps #destroy" do
route_for(:controller => "sites", :action => "destroy", :id => "1").should delete("/sites/1")
end
end
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
# a more detailed example where we make some actions available through /designs, and others only through /site/42/designs:
describe DesignsController do
describe "route generation" do
it "does not map #index at all" do
# be_routable() is a simple test - does it croak or not?
route_for( {:controller => "designs", :action => "index" } ).should_not be_routable
route_for( {:controller => "designs", :action => "index", :site_id => "42" } ).should_not be_routable
end
it "maps #new, but only when nested with the site" do
# backward-compatible assertion, still tests normal success case:
route_for(:controller => "designs", :action => "new", :site_id => "42" ).should == "/sites/42/designs/new"
route_for(:controller => "designs", :action => "new").should_not be_routable
end
it "does not map #show" do
route_for(:controller => "designs", :action => "show", :id => "1").should_not be_routable
route_for(:controller => "designs", :action => "show", :id => "1", :site_id => "42" ).should_not be_routable
end
it "maps #edit when not nested" do
# be_<method>() has a different failure condition. It is recommended to use it only for negative cases.
# So, when this passes, it's fine.
route_for(:controller => "designs", :action => "edit", :id => "1" ).should be_get("/designs/1/edit")
# ... but when it fails, it's less than informative. .should == is more
# informative, because it doesn't catch any of the possible exceptions.
route_for(:controller => "designs", :action => "edit", :id => "1" ).should == "/designs/1/edit"
# .should_not be_get() is useful for verifying negative cases.
# Because it catches the exceptions and returns false, we don't have to
# jump through any hoops to check those cases in our spec:
route_for(:controller => "designs", :action => "edit", :site_id => "42", :id => "1" ).should_not be_get("/sites/42/designs/1/edit")
end
it "maps #create, but only when nested with the site" do
route_for(:controller => "designs", :action => "create").should_not be_routable
route_for(:controller => "designs", :action => "create", :site_id => "42" ).should be_post("/sites/42/designs")
# backward-compatible assertion, still tests normal success case:
route_for(:controller => "designs", :action => "create", :site_id => "42" ).should == {:path => "/sites/42/designs", :method => :post }
end
it "maps #update" do
route_for(:controller => "designs", :action => "update", :id => "1").should be_put("/designs/1")
end
it "maps #destroy" do
route_for(:controller => "designs", :action => "destroy", :id => "1").should be_delete("/designs/1")
end
end
end
class RouteFor
include ::Spec::Rails::Example::RoutingHelpers::ParamsFromQueryString
TEST_ROUTABILITY="<test routabilty only>"
def initialize(example, options)
@example, @options = example, options
end
def routable?
begin
self == TEST_ROUTABILITY
rescue ActionController::RoutingError
return false
rescue ActionController::MethodNotAllowed
return false
rescue Exception => e
if e.to_s =~ /generated path.*did not match/
# that's not a routing error, just a path-matching issue - so it must have worked fine
return true
end
raise # re-raise the exception
end
return true
end
def ==(expected)
if Hash === expected
path, querystring = expected[:path].split('?')
path_string = path
path = expected.merge(:path => path)
else
path, querystring = expected.split('?')
path_string = path
end
params = querystring.blank? ? {} : @example.params_from_querystring(querystring)
if path_string == TEST_ROUTABILITY
# debugger
end
@example.assert_generates(path_string, @options, params)
unless path_string == TEST_ROUTABILITY
@example.assert_recognizes(@options, path, params)
end
true
end
def check_with_method(expected)
begin
result = ! self.==(expected)
rescue ActionController::MethodNotAllowed
return false
rescue Exception => e
e.to_s =~ /found extras/ and return false
raise e
end
return true
end
# these allow the .should_not be_get() expectations.
# they try not to raise the usual exceptions.
def get?(expected)
check_with_method({:path => expected, :method => :get})
end
def post?(expected)
check_with_method({:path => expected, :method => :post})
end
def put?(expected)
check_with_method({:path => expected, :method => :put})
end
def delete?(expected)
check_with_method({:path => expected, :method => :delete})
end
# these allow an alternate form of ==, shortening the required syntax
# but allowing exceptions to be displayed as desired for positive cases.
# usage: route_for(...).should get("/path")
def get(expected)
self == {:path => expected, :method => :get}
end
def post(expected)
self == {:path => expected, :method => :post}
end
def put(expected)
self == {:path => expected, :method => :put}
end
def delete(expected)
self == {:path => expected, :method => :delete}
end
end
# ahem. After originally posting, I realized that I hadn't tested .should get() the way I thought I had.
# those aren't working. So, we can punt on that part, or maybe figure out a way to get it.
# that 'ahem' might obsolete the following.
I'm not 100% perfectly satisfied with the discrepancy between .should post() and .should_not be_post().
They do each read intuitively, which I like. .should be_post() does also read okay, and it operates
fine for success cases. I'm not loving its uninformative failure case, but if the scaffold demonstrates
with .should post(), it's probably fine.
The part that leaves me unsettled is, route_for().should_not post(). If you specify this, all the
expected failures will be uncaught, resulting in exceptions when should_not post() *should* technically
pass. If the post() part is true, a reasonable message will appear griping that it should_not have
passed.
Maybe we can have get() and friends be implemented as wrap_equals(), which catches the exceptions and
adds a suggestion that if they were hoping to should_not() the test, they should use be_foo() instead
of foo(). UPDATE: I have tested a version of this exception-wrapping, using be_posty() method instead
of a post() method, and it works very well. Failures do show a very reasonable suggestion to do a more
useful thing for a should_not test.
An optional match_negative() method on custom matchers would allow us to do some nice things here.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment