[rspec-users] Possible improvements to routing spec API

Wincent Colaiuta win at wincent.com
Mon Jul 5 10:04:03 EDT 2010

El 05/07/2010, a las 13:56, David Chelimsky escribió:

> Nice overall. Much of the code belongs in Rails, though, so I'd like to try to get a patch in to Rails once we get this worked out. I'd like the rspec-rails matchers to be simple wrappers for rails' assertions wherever possible.

Well, I'm not sure if there is a problem with the Rails assertions. In my (albeit limited) experience I haven't yet seen a situation where they couldn't express something during testing. So as you point out, the job of RSpec should just be to expose those APIs within specs in a usable and effective manner. If we discover some limitation that we can't get around, then sure, the upstream assertions will need some work.

> I think having three separate matchers would land us in a good spot.

Agreed. That's why I added a matcher for each of assert_recognizes, assert_generates, and assert_routing (although so far in practice I've only had to use two of them).

> The wrapped rails assertions also accept a hash representing query params, so let's see if we can accommodate that as well:
>  get('/something', :ref_id => '1234').should route_to(....)

Yes, in my initial implementation I left that out seeing as I haven't yet run into a spec that needed it, but I did have it in mind when I wrote the "get" etc methods (in fact, when I first typed them in I started with 'def get path, options = {}', but then thought, YAGNI, better not add that in until I am sure I need it...)

> Something like:
>  describe "The router" do
>    it { should recognize(get('/wiki/foo').as("wiki#show", :id => 'foo') }
>    it { should generate(get('/wiki/foo').from("wiki#show", :id => 'foo') }
>    it { should route(get('/wiki/foo').to("wiki#show", :id => 'foo') }
>  end

Yes, the "action#controller" notation is a good idea. I actually added that to the gist that I posted already.

Not sure about nesting the get() method inside the matcher though. It seems that in most other places, we use hashes or literal values; eg:

  something.should do_something(:when => 'always', :how => 'quickly')

So, it would be more consistent with existing style to write:

  it { should recognize(:get => '/wiki/foo').as('wiki#show', :id => 'foo') }

But then we lose some of the conciseness I was looking for in my original proposal. In any case we also lose the similarity with controller specs (where "get" is the first thing on the line) which I was also looking for.

Nevertheless, your choice of verbs and prepositions work well together; "recognize as", "generate from" and "route to" all read fairly well.

Just to clarify, are you suggesting that:

- "recognize as" wrap assert_recognizes?
- "generate from" wrap assert_generates?
- "route to" wrap assert_routing?

If so, one misgiving I have is that we still have a unidirectional preposition ("to") being used in conjunction with the bidirectional assertion. 

An interesting consequence of the language you propose is that the focus of testing changes ie. in the current generator templates, we have:

  describe FooController do
    describe 'routing' do
      it 'recognizes and generates #index' do
        { :get => 'bar' }.should route_to(:controller => 'bar', :action => 'index')

So the focus is on controller actions, and the subject of each 'it' block is a literal hash representing a request to that controller and action.

The new language puts the router at the center and the implicit subject of each 'it' block is really the router itself.

> That's definitely nice and clean, and it makes it easier to align the names with the rails assertions, which I think adds some value. The output could be formatted something like this:
>  The router
>    should recognize get('/wiki/foo') as wiki#show with :id => 'foo'
>    should generate get('/wiki/foo') from wiki#show with :id => 'foo'
>    should route get('/wiki/foo') to wiki#show with :id => 'foo'
> Thoughts?

I think it would be nicer to format that as "GET /wiki/foo" (ie. HTTP verb followed by path) rather than embedding the literal text of the "get('/wiki/foo')" method call.

In any case, I converted another RESTful resource over to the syntax I mentioned earlier, now using the "controller#action" syntax:

  describe IssuesController do
    describe 'routing' do
      example 'GET /issues' do
        get('/issues').should map('issues#index')

      example 'GET /issues/new' do
        get('/issues/new').should map('issues#new')

      example 'GET /issues/:id' do
        get('/issues/123').should map('issues#show', :id => '123')

      example 'GET /issues/:id/edit' do
        get('/issues/123/edit').should map('issues#edit', :id => '123')

      example 'PUT /issues/:id' do
        put('/issues/123').should map('issues#update', :id => '123')

      example 'DELETE /issues/:id' do
        delete('/issues/123').should map('issues#destroy', :id => '123')

Which, if you really care about conciseness, can be written as:

  describe IssuesController do
    describe 'routing' do
      it { post('/issues').should map('issues#create') }
      it { get('/issues').should map('issues#index') }
      it { get('/issues/new').should map('issues#new') }
      it { get('/issues/123').should map('issues#show', :id => '123') }
      it { get('/issues/123/edit').should map('issues#edit', :id => '123') }
      it { put('/issues/123').should map('issues#update', :id => '123') }
      it { delete('/issues/123').should map('issues#destroy', :id => '123') }

These get formatted in the spec output as:

      GET /issues
      GET /issues/new
      GET /issues/:id
      GET /issues/:id/edit
      PUT /issues/:id
      DELETE /issues/:id


      should map "issues#create"
      should map "issues#index"
      should map "issues#new"
      should map "issues#show" and {:id=>"123"}
      should map "issues#edit" and {:id=>"123"}
      should map "issues#update" and {:id=>"123"}
      should map "issues#destroy" and {:id=>"123"}

Respectively. By adding a description method to the matcher (see updated Gist: http://gist.github.com/464081), we can produce:

      should map POST /issues as issues#create
      should map GET /issues as issues#index
      should map GET /issues/new as issues#new
      should map GET /issues/123 as issues#show with {:id=>"123"}
      should map GET /issues/123/edit as issues#edit with {:id=>"123"}
      should map PUT /issues/123 as issues#update with {:id=>"123"}
      should map DELETE /issues/123 as issues#destroy with {:id=>"123"}

Which IMO looks pretty nice.


More information about the rspec-users mailing list