El 07/07/2010, a las 01:16, Lalish-Menagh, Trevor escribió:

> Hi David,
> 
> You make a good point. I was talking with a coworker about this
> problem, and he suggested a simpler format, that I think will coincide
> some with Wincent's thoughts. Here is my next stab
> (http://gist.github.com/466064):
> describe "routing" do
>  it "recognizes and generates #index" do
>    get("/days").should have_routing('days#index')
>  end

Very nice. I definitely like this better than the "map()" terminology that I 
used.

>  it "generates #index" do
>    get("/days").should generate('days#index')
>  end

Reads well, _but_ I think you've got it back-to-front. assert_generates, and 
therefore the corresponding matcher in RSpec, asserts that a _path_ can be 
generated from a set of _params_ (controller, action, additional params), but 
here you're asserting the opposite.

(One thing to note is that assert_generates really only does assert about the 
_path_, not the method + path, but it's still nice to write it as "get(path)" 
just for symmetry with the reverse version of the spec.)

>  it "recognizes #index" do
>    ('days#index').should recognize get("/days")
>  end

This one is back-to-front too; assert_recognizes is an assertion that a path 
(this time method + path) is recognized as a particular route (action, 
controller, additional params) but here you're asserting the opposite.

If we're really serious about using the same verbs as the Rails assertions, and 
we want to forward and reverse versions of the spec to read in the same way, we 
could use something like:

  specify { get('/days').should recognize_as('days#index') }
  specify { get('/days').should generate_from('days#index') }

If we don't mind swapping the order around

  specify { get('/days').should recognize_as('days#index') }
  specify { 'days#index'.should generate('/days') }

Note that in the "generate" spec here I drop the "get()" seeing as Rails isn't 
actually looking at the method, and it is just verbiage IMO to start nesting 
other method calls inside the generate() matcher.

Weighing up the two orders, I prefer the first pair of specs, I think, because:

- the repetition of the pattern makes it easier to remember and apply.

- if you have to supply additional parameters (as you do in your later 
example), using the first format you can write ('days#index', :student_id => 
'1') whereas with the second format you are obliged to involve another method 
like with() in order to express it as "('days#index').with(:student_id => '1')".

>  describe "nested in students" do
>    it "recognizes #index" do
>      ('days#index').with(:student_id => "1").should recognize
> get("/students/1/days")
>    end
>  end
> end

This is another back-to-front one, I think. You're not recognizing here, but 
generating.

> Notes:
> - I am using have_routing, recognize, and generate because those are
> the verbs used in Rails for the functions we are wrapping.

Seems like a good idea that we should definitely try to do, although with one 
reservation; if we feel that the language is confusing or suboptimal then we 
should feel free to change it.

I personally have lingering doubts about the Rails language because I think it 
_is_ confusing in a way. Look at the way you used recognize and generate 
back-to-front in this message. And I know that every time I want to make a 
comment about "assert_recognizes" and "assert_generates" I end up 
double-checking the API docs just to make sure that I'm about to use them in 
the right way.

> - I like using the word "with" to represent "extras," in the Rails API
> (see 
> http://edgeapi.rubyonrails.org/classes/ActionDispatch/Assertions/RoutingAssertions.html),
> since I think it fits here, and we already use with in RSpec in other
> places (like stubs, etc.).

I don't really like it; I think it adds unnecessary verbiage to the specs.

> - I like using get('path') since it is similar to the routing calls in
> the Rails 3 route file, and I think it is easy to intuit.
> - We can use the hash notation to conform to Rails 3, with an option
> to provide a full hash as well (Rails 2 style).

Yes, the example I posted at http://gist.github.com/464081 does that. 

> - The format still reads like English, and using "have_routing"
> instead of "routes_to" avoids the "_to" and "_from" problem that we
> have been talking about, PLUS it makes it easier to draw a
> relationship between the RSpec command and the Test::Unit command
> (assert_routing).

Yeah, I think "have_routing" is a definite improvement over "map".

I am less convinced that "recognize_as" is an improvement over the highly 
readable "map_to".

I am less attached to "map_from", though, and think "generate_from" might be an 
improvement.

Like I said above, I think the Rails terminology does have the potential to be 
confusing, so I don't necessarily feel "wedded" to it.

> - Using these verbs still allow "should_not" to make sense.

Yes, I think that's important.

One thing I wanted to add is that I discovered in my testing that the existing 
"be_routable" matcher isn't very useful. This is because it relies on 
"recognize_path", which I am not sure is public API or not, and 
"recognize_path" sometimes recognizes the unrecognizable... For example:

  in config/routes.rb:
    resource :issues, :except => :index

  visit issues#index (/issues) in your browser:
    get a RoutingError (expected)

  in your spec:
    recognize_path('/issues', :method => :get) # recognizes!

So this means you can't do stuff like get('issues#index').should_not 
be_routable, because the Rails recognize_path() method will tell you that it 
_is_ routable. Seems that it is only useful for a subset of unroutable routes 
(like completely non-existent resources, for example).

If you look in the Gist you'll see that I work around this limitation by adding 
a "be_recognized" matcher which doesn't use "recognize_path()" under the hood. 
Instead it uses "assert_recognizes" and decides what to do based on what kind 
of exception, if any, gets thrown by it. So you can write:

  specify { get('/issues').should_not be_recognized }

(Funnily enough, assert_recognizes() _does_ use recognize_path() under the 
hood, but it's evidently doing some additional set-up and tear-down to make it 
work. It seems like wrapping assert_recognizes rather than replicating its 
logic, though, is the right thing to do.)

Cheers,
Wincent

_______________________________________________
rspec-users mailing list
rspec-users@rubyforge.org
http://rubyforge.org/mailman/listinfo/rspec-users

Reply via email to