[rspec-users] Depot app, Demeter's law and troubles cleanly spe cing

David Chelimsky dchelimsky at gmail.com
Mon Apr 27 10:30:19 EDT 2009


On Sun, Apr 26, 2009 at 9:20 PM, Dan North <tastapod at gmail.com> wrote:
> 2009/4/24 Stephen Eley <sfeley at gmail.com>
>>
>> On Thu, Apr 23, 2009 at 2:19 PM, Fernando Perez <lists at ruby-forum.com>
>> wrote:
>>
>> [...]
>>
>> # Assume an article with no comments and a params structure
>> # have already been set up in before(:each)
>>
>> it "should make a new comment belonging to the article" do
>>  post :create, :comment => @my_valid_params
>>  @my_article.should have(1).comment
>> end
>>
>> How clean is that?  Now just a couple more specs for *invalid* params
>> and for where the method directs to, and you're basically done.
>
> Amen to that.
>
>>
>> > Mocking and stubbing is starting to get ugly now.
>>
>> That much is true.  You'll notice I didn't mock or stub anything up
>> there.  I honestly think the controller code by the RSpec scaffold
>> generator is the wrong approach; it's specing implementation, not
>> behavior.  Even worse, it's specing the implementation details of
>> *model* code, which shouldn't even be a consideration here.  My way
>> breaks isolation (it hits the database) but it's a lot simpler and
>> focuses on behavior.
>
> And amen to that too.
>
> Even within an rspec example I think of each line of code as a Given
> (setup), When (event) or Then (outcome). Usually when I see a line doing
> more than one of these - say calling a method and verifying its result - I
> break it out into separate event and outcome lines.
>
> The reason for this is that the givens and outcomes can tinker with whatever
> they want. They can create mocks, poke around in the database, wire up
> models, whatever. The event lines though - the Whens - can only interact
> with the object like client code would. In other words they can't know that
> an object is a mock, or that a value got into a database because you poked
> it there.
>
> Your example is great. You have an event line that interacts with the
> controller through a regular HTTP POST, like a client would, and you verify
> that by checking the database via the ActiveRecord model object. There is no
> need for a mock here if your example is checking that data ends up in the
> database (although that does make it more of an integration test than a
> behavioural spec). Because of rails's evil insistence that a "model" is just
> a persistence strategy, they are pretty much the same thing.
>
>> Is it possible to get behavior focus *and* isolation?
>
> Yes, but it only has value on a per-line-of-code-in-your-spec basis. Your
> givens and outcomes shouldn't care about isolation - your events should only
> be exercising behaviour that is available in the object you are describing.
>
> It gets more complicated when a single line of code both exercises behaviour
> and does verification. I find breaking these out helps my sanity.
>
>>  I started to
>> think about that.  And then I realized, in accordance with the "If
>> it's hard to spec you're probably doing it wrong" principle,
>> that...well, Rails is probably doing it wrong.  Controllers in Rails
>> are just doing the Wrong Thing.  That's why specing them is so
>> painful, and why so many of us skip trying.

I've struggled with this bit myself, as I suspect many of us who did
TDD before Rails have.

RSpec's mocking/stubbing framework just got a contribution that allows
you to do this in a controller spec:

Thing.stub_chain(:all, :with_some_scope, :perhaps_another).
  and_return([stub_model(Thing)]

This let's you stub a chain of named scopes, for example, in a rails
controller. Similarly, with models:

member.stub_chain(:addresses, :first, :zip_code)

Admittedly, this can result in an excuse for poor design choices. In
this example, the first address probably has some meaning that could
be exposed through a method like primary_address() or some such. Even
in that case, I think that allowing
member.stub_chain(:primary_address, :zipcode) would be preferable over
adding methods like primary_address_zipcode(). Of course, a little
method_missing magic could solve that as well :) I guess it's good to
have options.



>>
>> What's the right way?  I'm still pondering.
>
> Me too!
>
>>
>> Yeah, I'm a smartass.
>
> Well you seem to be on the right lines to me.
>
> Cheers,
> Dan
>
>
> _______________________________________________
> rspec-users mailing list
> rspec-users at rubyforge.org
> http://rubyforge.org/mailman/listinfo/rspec-users
>


More information about the rspec-users mailing list