[rspec-users] sharing specs in a subclass

Scott Taylor
2008-01-24T15:19:13Z

On Jan 24, 2008, at 4:36 AM, Pat Maddox wrote:
>
> On Jan 22, 2008 10:26 PM, David Chelimsky <dchelimsky at gmail.com>
> wrote:
>> On Jan 23, 2008 12:04 AM, Scott Taylor
>> <mailing_lists at railsnewbie.com> wrote:
>>>
>>> On Jan 23, 2008, at 12:02 AM, David Chelimsky wrote:
>>>
>>>> On Jan 22, 2008 10:49 PM, Jonathan Linowes
>>>> <jonathan at parkerhill.com> wrote:
>>>>> Hi
>>>>>
>>>>> I''ve spec''d a class and they pass.
>>>>>
>>>>> Now I''d like to assure that any subclass of this
class also passes
>>>>> the same specs.
>>>>> Any suggestions for a clever way to handle this?
>>>>> I''d prefer to keep the existing specs as is (eg
instead of moving
>>>>> everything into shared behaviors, or doing something to all
the
>>>>> ''describe'' lines)
>>>>
>>>> How about:
>>>>
>>>> [Subclass1, Subclass2, BaseClass].each do |klass|
>>>>   describe klass do
>>>>   ...
>>>>   end
>>>> end
>>>
>>> That''s sort of funny, being that I posted this solution on
>>> Courtney''s
>>> blog yesterday.
>>
>> I saw that. I''ve also done this before :)
>>
>>> Is this what you actually use in this sort of situations?  Are
there
>>> other (better, or worse) alternatives?
>>
>> I usually stick w/ shared example groups, but I sometimes use an
>> iterator like this. Not sure why. I''ll think about it.
>
> I think using an iterator here is weird, and muddles the intent of
> the specs.
>
> Think of what it means to define a class and a subclass.
>
> class Foo
>   ... some behavior ...
> end
>
> class Bar < Foo
>   ... all of Foo''s behavior ...
>   ... some specialized behavior ...
> end
>
> You don''t define all the behavior in Bar - you get a bunch of it
from
> Foo for free, and then specialize whatever you need.  Your specs
> should be similar in spirit.

In a normal setting, I''d agree with you.  In the case where I used
it, there was STI involved, and the subclasses had almost no unique
behaviour (except one or two methods which were over-ridden).

>
> shared_examples_for("the foo role") do
>   it "should have some behavior"
>   ...
> end
>
> The downside is you end up with
>
> describe Foo do
>   it_should_behave_like "the foo role"
> end
>
> to specify the behavior, which feels pretty weak to me.

The trouble with the shared specs is that you have to do the same
setup multiple times, in various specs.

>
> Though I assume you feel somewhat similarly, so I''d be interested
to
> know when you use an iterator for full example groups.
>
> Actually one example I remember is when I wrote a macro that added a
> bunch of methods to a class...I wrote the initial example group for
> just one attribute name.  Then when I extracted the macro, I just
> iterated through a bunch of names over the example group.  So it can
> be useful.  Although a whole class feels too coarse grained for that
> approach, to me.
>
> I do use iterators a bunch for stuff like
>
> [:name, :email, :phone, :address].each do |field|
>   it "should require a #{field}" do
>     u = build_user(field => nil)
>     u.should_not be_valid
>     u.should have(1).error_on(field)
>   end
> end
>
> Also, since I''m close to the subject, here''s a little
tip.  I''ve seen
> people write specs like:
>
> it "should require valid attributes" do
>   [:name, :email, :phone, :address].each do |field|
>     u = build_user(field => nil)
>     u.should_not be_valid
>     u.should have(1).error_on(field)
>   end
> end
>
> but that sucks, because if the example fails, you don''t have any
clue
> why.  Cause it''ll just say that user is invalid, but
doesn''t tell you
> which field is blank.

Yeah, I agree.  It''s also violating the one test per test case rule.
If you had ordered the tests in the opposite manner, it wouldn''t be a
problem.

I usually still use valid?, even though it''s less informative, since
I let FixtureReplacement take care of making it a valid record in the
first place:

it "should not be valid with a firstname" do
   new_user(:firstname => nil).should_not be_valid
end

it "should be valid with valid attributes" do
   new_user.should be_valid
end

Scott