[rspec-users] Possible improvements to routing spec API

Wincent Colaiuta win at wincent.com
Thu Jul 8 02:25:56 EDT 2010

El 08/07/2010, a las 04:45, Lalish-Menagh, Trevor escribió:

> OK, here is an idea. I was thinking about how to make routing tests
> that make sense. I agree with Wincent that the Rails verbiage for the
> routing tests is confusing, but what is NOT confusing is the new
> routing format, so why not try out this format
> (http://gist.github.com/467563):
> describe 'routing another way' do
>  it { should have_resources(:days) }
>  it { should get('/days' => 'days#index') }
>  it { should match('/days' => 'days#index', :via => :get) }
>  it { should recognize('/days', :via => :get).as('days#index') }
>  it { should generate('days#index').from('/days', :via => :get) }
>  it { should recognize('/students/1/days', :via =>
> :get).as('days#index', :student_id => '1') }
> end

[snipped for brevity]

> it { should generate('days#index').from('/days', :via => :get) }

Hehe. You got it back-to-front again. "assert_generates" asserts that a _path_ is generated from a set of params (action, controller, additional params). Here you are doing the opposite, saying that certain params (action, controller) get generated from a path.

> The Rails test read backwards, look at this example from
> http://edgeapi.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html:
> # assert that POSTing to /items will call the create action on ItemsController
> assert_recognizes({:controller => 'items', :action => 'create'},
> {:path => 'items', :method => :post})
> The comment reads like the inverse of the actual method call. I think
> that recognize(path).as(options) reads better (more like the comment
> above).

I think that's the general pattern of pretty much all Test::Unit assertions which compare two parameters. Where RSpec tends to say:

  actual.should == expected

Test::Unit tends to say:

  assert expected, actual

> Thoughts?

Well, this comes back to the eternal question of specing things like validations. Where the spec ends up being an almost identical re-statement of the implementation:

  validates_presence_of :foo
  it { should validate_presence_of(:foo) }

Are you testing the Rails validation code itself (which is already tested)? Are you testing that you remembered to include the validation itself? Do you trust your "validate_presence_of" matcher? Are you testing your ability to cut and paste? etc.

Well, same questions can arise with routing when the implementation and spec look like this:

  resource :days
  it { should have_resource(:days) }

So, when I see this kind of thing, I think _what_ are we trying to achieve in our routing specs and _why_? At least for me, the answers are:

  1. Confirm: To confirm that the routes I've defined actually do what I think they do

  2. Describe: To serve as a complete, explicit, readable specification of the behavior I expect from my routes

And finally:

  3. That the Rails routing code actually works as advertised

This last one is a bit dubious, but the truth is the Rails 3 router is a complete re-write and bugs in it and incompatibilities with the old router are being found and squished constantly. An exhaustive set of routing specs can definitely help to uncover undiscovered edge cases.

So, bearing in mind those goals, what I actually need from RSpec in order to achieve them is:

  - most importantly, a way of asserting that a user action (eg. HTTP verb + path + params) gets routed to a given controller + action with certain additional parameters (ie. a wrapper for assert_recognizes)

  - less importantly, a way of asserting that a set of params (action, controller, additional params) generates a particular path (ie. a wrapper for assert_generates); this is less important for me because in practice close to all (98%) of my URL generation is done with named URL helpers, and I can test those explicitly if I want

  - as syntactic sugar, a way of combining the above two assertions into one for those cases where the mapping is perfectly bidirectional (ie. a wrapper for assert_routing).

With these tools I can achieve pretty much everything I need: not only test that user actions end up hitting the right spots in my application, but also specify clearly and explicitly what I expect those mappings to be.

So for me, anything else doesn't really help me achieve my goals. The "have_resource(s)" matcher, for instance, doesn't help me and in fact actually undermines my goal of providing a complete and explicit specification of how my routes work.

The "recognize" and "generate" matchers you suggest obviously are "on target" to help me fulfill my goals. Of these, the "recognize" one is the most important one though.

Of the "match" and "get" matchers you suggest, seeing as they both wrap the same thing (assert_routing), one of them would have to go. I'd probably ditch "match" because it is just a repetition of the router DSL method of the same name, and my goal here isn't just to repeat that DSL in my specs.

In fact, if you look at my most important goal -- asserting that a user action (HTTP verb, path, params) hits a specific end point (controller + action + additional params) -- you'll understand why, in my proposal, all of my specifications start with "get"/"post" etc followed by path, params and then controller/action. It's not just for resonance with other parts of RSpec like where we use "get" etc in controller specs.

But if the goal is just to wrap the 3 Rails assertions as faithfully as possible, then you wind up with a different proposal. What David has posted is probably the closest to this goal.

And if the goal is make the specs map as closely as possible onto the language of config/routes.rb, then you wind up with a different proposal still... (ie. the one you just made).

So, I guess what I'm saying here is I'd like to hear _what_ people are wanting to achieve in their routing specs and _why_; and then to ask what kind of means RSpec should provide to best achieve those goals.


More information about the rspec-users mailing list