Hi folks, Can anyone share some accumulated wisdom about the best way to spec mixins in general, and (Jamis Buck-style) ActiveRecord "concerns" in particular? The standard situation here is that there''s a bunch of functionality, related by concept if not by implementation, that one wants to inherit in many different classes (e.g. ActiveRecord models) without having to actually use subclassing -- straightforward enough. But since BDD best practice encourages one expectation per example and no mocking in the behaviour setup, the specification for this shared functionality is often spread across many behaviours, each of which may need to do its own setup and teardown. So, how best to mix the mixin spec in with the spec for each class that uses the mixin (IYSWIM)? I''ve tried several permutations of helpers, spec mixins, shared-shared behaviours and so on, but can''t find anything which is persuasively neat and DRY while still working reliably. One point of contention is that the mixin''s behaviours might need to do things like instantiate the target class with specific arguments in before :each (or call some other class method, if the mixin provides some) so it''s not really good enough for the target spec to just squirrel away a prebuilt object in an instance variable. Any advice, please? Cheers, -Tom
just a short advice: describe MyModule do it "should do something" do # The module is automatically mixed into your spec end end Aslak On 11/1/07, Tom Stuart <tom at experthuman.com> wrote:> Hi folks, > > Can anyone share some accumulated wisdom about the best way to spec > mixins in general, and (Jamis Buck-style) ActiveRecord "concerns" in > particular? > > The standard situation here is that there''s a bunch of functionality, > related by concept if not by implementation, that one wants to inherit > in many different classes (e.g. ActiveRecord models) without having to > actually use subclassing -- straightforward enough. But since BDD best > practice encourages one expectation per example and no mocking in the > behaviour setup, the specification for this shared functionality is > often spread across many behaviours, each of which may need to do its > own setup and teardown. > > So, how best to mix the mixin spec in with the spec for each class > that uses the mixin (IYSWIM)? I''ve tried several permutations of > helpers, spec mixins, shared-shared behaviours and so on, but can''t > find anything which is persuasively neat and DRY while still working > reliably. One point of contention is that the mixin''s behaviours might > need to do things like instantiate the target class with specific > arguments in before :each (or call some other class method, if the > mixin provides some) so it''s not really good enough for the target > spec to just squirrel away a prebuilt object in an instance variable. > > Any advice, please? > > Cheers, > -Tom > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
On Nov 1, 2007, at 7:30 PM, aslak hellesoy wrote:> just a short advice: > > describe MyModule do > it "should do something" do > # The module is automatically mixed into your spec > end > end > > AslakI suppose it really depends on how static/dynamic the module is. This seems to work well for a relatively static case, but how you spec out a module like Enumerable in this manner (in which a series of methods is added to class if a method is implemented in the class - in the case, #each)? Scott
> > just a short advice: > > > > describe MyModule do > > it "should do something" do > > # The module is automatically mixed into your spec > > end > > end > > > > Aslak > > I suppose it really depends on how static/dynamic the module is.Add an additional describe block for the class that you''re including the module into, and then setup a genericly named object in the before block and then include the shared behaviours from the module''s spec module. Like this: # page_spec.rb describe Page, "should include publishing features" do include PublishableSpec before(:each) do @model = Page.new(:title => "title", :content => "content", :published => false) end it_should_behave_like "Publishable" # include the shared tests end # post_spec.rb describe Post, "should include publishing features" do include PublishableSpec before(:each) do @model = Post.new(:title => "title", :content => "content", :published => false) end it_should_behave_like "Publishable" # include the shared spec end # publishable_spec.rb module PublishableSpec describe "Publishable", :shared => true do it "should have a published marker" do @model.save! @model.should_not be_published end it "should be publishable" do @model.save.publish! @model.should be_published end end end I can post more if that''s unclear (showing the actual AR classes and mixin), but it seems to work well and eliminates duplication. I hope the formatting on this comes through... Jim Lindley http://jimlindley.com
On 6 Nov 2007, at 02:00, Jim Lindley wrote:> Add an additional describe block for the class that you''re including > the module into, and then setup a genericly named object in the before > block[...]> > # page_spec.rb > describe Page, "should include publishing features" do > include PublishableSpec > > before(:each) do > @model = Page.new(:title => "title", :content => "content", > :published => false) > endYeah, this is fine for a simple mixin with only one shared behaviour, but the problem is that a real chunk of mixed-in functionality will probably need many behaviours that correspond to different initial states (provided you''re behaving yourself and not doing too much state setup within the individual examples). So, firstly, perhaps I''ll have ten shared behaviours to specify my mixin -- do I have ten calls to it_should_behave_like in the spec for each target class? And secondly, perhaps each of those shared behaviours is talking about an object with different initial state, e.g. constructed with different arguments (a published thing, an unpublished thing, a published but deleted thing) -- how do I arrange for that to happen? Ten corresponding behaviours each containing the appropriate before(:each) and a call to it_should_behave_like? Or arrange for the shared behaviours to somehow do the initialisation themselves, e.g. by doing some kind of parameterized helper gymnastics where you pass in the child class object and the shared behaviour calls #new on it? None of this is impossible to do, it''s just that it all feels very repetitious and difficult in contrast to the elegant solutions that people often post here. I''m hoping that this sort of "okay, you understand the basics, but NOW what?" issue is the kind of thing that the hotly-anticipated RSpec book will address, because any information about best practice in this area is really lacking, but I fear it might not be that kind of book! Cheers, -Tom
On Nov 6, 2007 8:11 AM, Tom Stuart <tom at experthuman.com> wrote:> On 6 Nov 2007, at 02:00, Jim Lindley wrote: > > Add an additional describe block for the class that you''re including > > the module into, and then setup a genericly named object in the before > > block > [...] > > > > # page_spec.rb > > describe Page, "should include publishing features" do > > include PublishableSpec > > > > before(:each) do > > @model = Page.new(:title => "title", :content => "content", > > :published => false) > > end > > Yeah, this is fine for a simple mixin with only one shared behaviour, > but the problem is that a real chunk of mixed-in functionality will > probably need many behaviours that correspond to different initial > states (provided you''re behaving yourself and not doing too much state > setup within the individual examples). > > So, firstly, perhaps I''ll have ten shared behaviours to specify my > mixin -- do I have ten calls to it_should_behave_like in the spec for > each target class? > > And secondly, perhaps each of those shared behaviours is talking about > an object with different initial state, e.g. constructed with > different arguments (a published thing, an unpublished thing, a > published but deleted thing) -- how do I arrange for that to happen? > Ten corresponding behaviours each containing the appropriate > before(:each) and a call to it_should_behave_like? Or arrange for the > shared behaviours to somehow do the initialisation themselves, e.g. by > doing some kind of parameterized helper gymnastics where you pass in > the child class object and the shared behaviour calls #new on it? > > None of this is impossible to do, it''s just that it all feels very > repetitious and difficult in contrast to the elegant solutions that > people often post here.There really isn''t a great solution for this problem right now, but there are a few initiatives that will be helpful in getting us there. Nested specs, classes/methods (as an alternative to describe/it), turning examples into methods (so they can get overridden), etc, etc.> I''m hoping that this sort of "okay, you > understand the basics, but NOW what?" issue is the kind of thing that > the hotly-anticipated RSpec book will address, because any information > about best practice in this area is really lacking, but I fear it > might not be that kind of book!Well - there will be some of that, and we''ll certainly consider including something about this issue. It''s fairly common. Cheers, David> > Cheers, > -Tom > > _______________________________________________ > rspec-users mailing list > rspec-users at rubyforge.org > http://rubyforge.org/mailman/listinfo/rspec-users >
> Yeah, this is fine for a simple mixin with only one shared behaviour, > but the problem is that a real chunk of mixed-in functionality will > probably need many behaviours that correspond to different initial > states (provided you''re behaving yourself and not doing too much state > setup within the individual examples). >Tom, there is likely a better path then the one I''m going down, and I would love to hear it. I am no RSpec expert. How big are the modules you''re including? For the modules I do this with it doesn''t seem to get out of hand. The example I gave above was a simplifed version of a module spec, not the whole thing. The real one has many more behaviour blocks, and in the before block a couple hashes for use inside the module spec for creating other scenarios. I''m not doing much setup inside the module spec. If the module has a couple aspects to its behaviour I usually end up with just 2 or 3 describe blocks each with about 20 it blocks, and testing its inclusion into AR classes just needs a before block setting up a half dozen objects. If it''s getting too complicated to spec out maybe the module is doing too much and should be split up? Jim
On 6 Nov 2007, at 14:39, David Chelimsky wrote:>> I''m hoping that this sort of "okay, you >> understand the basics, but NOW what?" issue is the kind of thing that >> the hotly-anticipated RSpec book will address, because any >> information >> about best practice in this area is really lacking > Well - there will be some of that, and we''ll certainly consider > including something about this issue. It''s fairly common.Great! My personal experience has been that the nuts and bolts of RSpec are pretty easy to pick up from the really rather good online documentation -- it''s the details of how to actually apply this stuff to real projects that gets people confused about what "the right way" is. (Witness the many discussions about how to spec ActiveRecord validations, etc.) Obviously a lot of this stuff is a matter of opinion and design preference rather than cold mathematical fact, but someone putting a stake in the ground and proclaiming a few best practices (at the right level of abstraction, i.e. general enough to be applicable but concrete enough to be useful) would be incredibly welcome. Can''t wait! Cheers, -Tom
On 6 Nov 2007, at 14:40, Jim Lindley wrote:> Tom, there is likely a better path then the one I''m going down, and I > would love to hear it. I am no RSpec expert.Neither am I! From David''s response it sounds as though there isn''t a particularly tidy way to solve this problem at the moment, so I guess we just need to muddle along in whatever way works.> How big are the modules you''re including? > For the modules I do this with it doesn''t seem to get out of hand. > If it''s getting too complicated to spec out maybe the module is doing > too much and should be split up?Possibly, but the proliferation of behaviours is mostly due to following Dave Astels'' "one expectation per example" guideline (http://daveastels.com/2006/08/26/one-expectation-per-example-a-remake-of-one-assertion-per-test/ ), which actually works out really well for regular specs, making them easier to write and understand. It''s just the impedance mismatch between this particular practice (i.e. split up big behaviours into lots of smaller ones) and RSpec''s limited support for sharing behaviours (i.e. you can''t group them together or supply parameters when referencing them) that causes the hassle, rather than (necessarily) a large or complicated mixin module. Cheers, -Tom