[rspec-users] get to a different controller

Zach Dennis zach.dennis at gmail.com
Thu Jan 7 11:42:28 EST 2010


On Thu, Jan 7, 2010 at 5:09 AM, Matt Wynne <matt at mattwynne.net> wrote:
>
> On 7 Jan 2010, at 07:22, Wincent Colaiuta wrote:
>
>> El 07/01/2010, a las 03:53, Phillip Koebbe escribió:
>>
>>> Wincent Colaiuta wrote:
>>>>
>>>> Well, there is more than one way to skin a cat, but the thing I like
>>>> about my proposed solution is that:
>>>>
>>>> - the specification of the behavior appears in the "describe" block that
>>>> corresponds to the controller where the behavior is implemented
>>>>
>>>> - but given that the implementing controller is an abstract one, you
>>>> actually test the behavior where it is actually exercised (ie. in the
>>>> subclasses)
>>>>
>>>> - you don't need to make a fictional controller which isn't actually
>>>> part of your application purely for testing purposes
>>>>
>>>> Looking at the gist you pasted it looks like it could be very
>>>> straightforward to test, especially if you're using a RESTful access
>>>> pattern.
>>>>
>>>> Your admin controllers all inherit from your abstract base class, and if
>>>> they're RESTful you know before you even start which actions they'll respond
>>>> to (the usual index, new, create etc). Perhaps they only respond to a
>>>> subset; but even if they only respond to one ("show" or "index", say) you
>>>> have enough of a common basis to test that the require_admin before filter
>>>> is actually hit and does what you think it should (ie. you only need to hit
>>>> "show" or "index", there is no need to test all actions).
>>>>
>>>> Cheers,
>>>> Wincent
>>>>
>>>
>>> Are you basically saying that you wouldn't worry about testing the
>>> before_filter in the base_controller at all?
>>
>> Exactly. The base_controller is never directly instantiated and its
>> "behaviour" is only ever manifested in the context of its subclasses, so you
>> can test the behavior by testing that it exists in the subclasses.
>>
>> As you have seen, you can't really test the abstract base_controller
>> directly itself because it has no actions.
>>
>>> Maybe part of the reason I am not "getting this" is my lack of
>>> familiarity with shared behaviors. If I share the behavior in the
>>> base_controller_spec, will it get tested when I run that spec? If it won't,
>>> then my concern is moot. If it will, then I'm just as confused as I was
>>> before.
>>
>> Shared behaviors are just a convenient way to define in behaviors once in
>> a single place, which will be exercised in multiple places, so that you can
>> keep things DRY.
>>
>> If you run the file with the "describe foo :shared => true" block in it,
>> nothing will actually happen.
>>
>> Your assertions get exercised only when you run other "describe" blocks
>> containing "it_should_behave_like ..."
>>
>> They can be useful not just in cases like this where you have an abstract
>> superclass. You can use them wherever you have a bunch of common behavior
>> across different classes.
>
> OK I'm going to bite on this one.
>
> Shared behaviours are indeed useful and a really nice idea. When I first
> started working with RSpec I used them exactly like this - I wrote no specs
> for abstract classes or modules, and wrote shared behaviour specs which I
> mixed in to all the classes that used the abstract class or module. I told
> myself: "this is much better - I'm not being distracted by the
> implementation details (inheritance, mixins) I'm just specifying the
> behaviour of the class".
>
>
> I think that method probably works great for small models, but I have found
> that over time on the Songkick codebase, I've come to rather dislike the
> shared behaviours. This is mainly, I think, because when I get a failure,
> the error message doesn't point me very clearly at which class was actually
> being tested when the failure occurred.

I don't really see a correlation between size and usefulness of shared
behaviours either through rspec's it_should_behave_like or a custom
macro. When you describe a class in a spec that class is prepended to
all of the example descriptions. For example:

  describe "a bar", :shared => true
    it "should serve up tequila" { ... }
  end

  describe Foo do
    it_should_behave_like "a bar"
  end

will produce:

  Foo should serve up tequila

If the top-level describe for an object isn't descriptive enough to
communicate what class/module/etc is being spec'ed then I see what
you're saying as a problem because the backtrace of a failed shared
example won't help either, since the backtrace stems from the file
that the shared example is stored in rather than the actual spec using
it (I wonder if there is a way around this).

> I also think I resent them because I
> know that for a popular module, I might be running the exact same specs over
> the exact same code several times, for no purpose.

This doesn't bother me a whole lot. While I don't want to needlessly
run examples multiple times to ensure something is working, I like err
on the side of my objects are behaving correctly rather than an object
includes a module. One of my reasons for this is because in the past
I've unfortunately had to find the hard way classes that were buggy
because of module inclusion order and one module overwrote another
module's method, yada yada yada. I have personally also gotten into a
groove for writing my own little shared macros and it makes it super
simple to build new classes which re-use that functionality and know
it actually works.

>
> What I now prefer to do is keep the interface between a module (or even an
> abstract class) and the class it's mixed into quite clean, and specify that
> re-usable unit in isolation, with an example using a temporary class created
> in the test. I might have one or two specs in the class which mixes in the
> module to prove that the module is mixed in and working, but most of the
> specs for the behaviour of that module itself will live alongside the module
> and run only once.

This is excellent advice regardless of using shared examples, although
it isn't always practical (clean interfaces are always practical, what
isn't is creating new specs with test classes). Some modules are
simply a grouping of certain behaviour that relies on other behaviour
existing on an object. Creating a new test class to put in a spec can
require a good amount of time and thinking based on the functionality
and where what you're module's functionality falls into the overall
chain of dependencies.

There are times to be disciplined and put in that time and thinking.
At other times, it's important to not misplace a bunch of time and
energy if what you're working on doesn't warrant it. Unfortunately,
making knowing when to make good decisions often comes from making bad
ones. So as Corey Haines would suggest, practice practice practice!

>
> As David said earlier there are strengths and weaknesses to both approaches
> so you have to find your own path here. I just wanted to share my
> experience.

Ditto.

-- 
Zach Dennis
http://www.continuousthinking.com (personal)
http://www.mutuallyhuman.com (hire me)
http://ideafoundry.info/behavior-driven-development (first rate BDD training)
@zachdennis (twitter)


More information about the rspec-users mailing list