Ashley Moran
2010-Jul-26 07:31 UTC
[rspec-users] Formatting shared example descriptions with test data
Hi I''m back again, and still on a quest to tame shared example to do my bidding. This time what I''m wondering is... is there any way to format shared example specdoc description output with data passed in with #let, or otherwise? eg: How do I get "1" and "2" into either of the placeholders <i> here? shared_examples_for "Comparable" do describe "comparing <i>" do it "defines equality for <i>" do object.should eq object end end end describe Integer do it_should_behave_like "Comparable" do let(:object) { 1 } end it_should_behave_like "Comparable" do let(:object) { 2 } end end (If I had to duplicate the 1 and 2 that wouldn''t be the end of the world, as long as the syntax for including the shared examples was simple enough.) Cheers Ash -- http://www.patchspace.co.uk/ http://www.linkedin.com/in/ashleymoran
Wincent Colaiuta
2010-Jul-26 07:55 UTC
[rspec-users] Formatting shared example descriptions with test data
El 26/07/2010, a las 09:31, Ashley Moran escribi?:> I''m back again, and still on a quest to tame shared example to do my bidding. This time what I''m wondering is... is there any way to format shared example specdoc description output with data passed in with #let, or otherwise? eg: How do I get "1" and "2" into either of the placeholders <i> here? > > shared_examples_for "Comparable" do > describe "comparing <i>" do > it "defines equality for <i>" do > object.should eq object > end > end > end > > describe Integer do > it_should_behave_like "Comparable" do > let(:object) { 1 } > end > it_should_behave_like "Comparable" do > let(:object) { 2 } > end > end > > (If I had to duplicate the 1 and 2 that wouldn''t be the end of the world, as long as the syntax for including the shared examples was simple enough.)Seems to me that including the same shared example group twice in the same "describe" block is a bit of an abuse, to be honest. I don''t think it was ever really intended to be used in that way. Your shared_examples_for should be written in such a way that they don''t need to reference "<i>"; ie: shared_examples_for "Comparable" do it ''defines equality'' do subject.should eq(subject) end end And then you use it like this: describe Integer do [1, 2].each do |i| describe i do it_should_behave_like ''Comparable'' end end end Obviously this is a toy example that you''ve given us, and I don''t see why you''d want to test 1 and 2 like that, but for the purposes of illustration... The specdoc output for that would look like: Integer 1 it should behave like Comparable defines equality 2 it should behave like Comparable defines equality I know you probably have some real example in mind hiding behind that toy example, but I believe anything you want to test can be written in the same way (ie. without needing to inject the "<i>" into your shared examples). Cheers, Wincent
Ashley Moran
2010-Jul-26 12:09 UTC
[rspec-users] Formatting shared example descriptions with test data
On Jul 26, 2010, at 8:55 am, Wincent Colaiuta wrote:> Seems to me that including the same shared example group twice in the same "describe" block is a bit of an abuse, to be honest. I don''t think it was ever really intended to be used in that way.You''re right, it clearly wasn''t intended for this. I''m trying to find the best way to express the behaviour I want without bending the current syntax of RSpec too much. This is indeed a toy example, so let me explain the real situation in more detail. I''m doing a small side project to make a checklist app. As part of that, I''m trying to extract out a library similar to Naked Objects[1]. One of the things that can be factored out is collections inside entities. So I currently have, as examples: class User extend DomainLib::Entity::ClassExtensions include DomainLib::Entity::InstanceExtensions collection :checklist_templates, of: :checklist_template, class_name: "ChecklistTemplate" end and class ChecklistTemplate extend DomainLib::Entity::ClassExtensions include DomainLib::Entity::InstanceExtensions collection :items, of: :item, class_name: "ChecklistTemplateItem" end Now one of the thing that bugs me about using ORM (eg ActiveRecord, DataMapper) features for this is you''re then faced with the dilemma of do you do an integration test of the collection functionality, which duplicates a lot of the testing effort put into the ORM, or do you mock this out, and risk having false positives because the ORM behaves differently than your test setup assumes? The solution I''m playing with is to extract shared contract (ie shared example groups) that you can mix into a spec for a host class (eg User, Checklist) above to prove the feature (here collections) works, without reference to the implementation. (The specs inside DomainLib prove the general case.) With the help of this spec_helper incantation: module SpecHelperObjectExtensions def contract(name, &block) shared_examples_for(name, &block) end end include SpecHelperObjectExtensions RSpec.configure do |c| c.alias_it_should_behave_like_to(:it_satisfies_contract, ''satisfies contract:'') end I''ve already been able to extracted contract, which is for Representation (basically, a view object that isn''t much more than a Struct): # Params: # * representation_class # * properties contract "Representation" do # ... # Setup and other examples omitted # ... describe "#==" do it "is true for Representations with the equal attributes" do representation_class.new(default_attributes).should eq representation_class.new(default_attributes) end it "is false if any property is different" do properties.each do |property| representation_class.new(default_attributes).should_not eq( representation_class.new(default_attributes_with_different_value_for(property)) ) end end end end This is fine for a class, but the behaviour I want to prove with a Collection needs to be mixed in once per collection, eg (the last two are made up): describe User do it_satisfies_contract "Entity Collection", for: "checklist_templates" it_satisfies_contract "Entity Collection", for: "groups" it_satisfies_contract "Entity Collection", for: "delegated_actions" end> describe Integer do > [1, 2].each do |i| > describe i do > it_should_behave_like ''Comparable'' > end > end > end > > ... > > I know you probably have some real example in mind hiding behind that toy example, but I believe anything you want to test can be written in the same way (ie. without needing to inject the "<i>" into your shared examples).So as you can see, in the real (non-toy) example there''s no object to be described: it''s an aspect of behaviour of the test subject, and one than can occur N times. I''m aware that I''m twisting RSpec quite a bit to try to achieve this. If you (or anyone) have any thoughts though, I''d love to hear them. This one is messing with my head a bit :) Cheers Ash [1] http://www.nakedobjects.org/ -- http://www.patchspace.co.uk/ http://www.linkedin.com/in/ashleymoran
Wincent Colaiuta
2010-Jul-26 14:44 UTC
[rspec-users] Formatting shared example descriptions with test data
El 26/07/2010, a las 14:09, Ashley Moran escribi?:> The solution I''m playing with is to extract shared contract (ie shared example groups) that you can mix into a spec for a host class (eg User, Checklist) above to prove the feature (here collections) works, without reference to the implementation. (The specs inside DomainLib prove the general case.) > > With the help of this spec_helper incantation: > > module SpecHelperObjectExtensions > def contract(name, &block) > shared_examples_for(name, &block) > end > end > include SpecHelperObjectExtensionsPersonally I wouldn''t do this. It makes it harder for anybody coming to your project to understand what''s going on, because they see this "contract ''foo'' do" construct and don''t know what it is unless they dig into your spec_helper. If you really want the word contract to appear in there I would just write the shared examples like this: shared_examples_for ''representation contract'' do ... end> RSpec.configure do |c| > c.alias_it_should_behave_like_to(:it_satisfies_contract, ''satisfies contract:'') > endAnd if you go with names like "representation contract" then you might want your alias to be just "it_satisifies" instead...> it "is false if any property is different" do > properties.each do |property| > representation_class.new(default_attributes).should_not eq( > representation_class.new(default_attributes_with_different_value_for(property)) > ) > end > endSo if I''m reading you correctly, this is where the "aspect of behavior that can occur N times" comes in, right? AFAIK the typical way to do this is via "macros" (ie. generating examples on the fly, typically keeping to examples of one assertion per iteration of the loop). So it''s just a minor tweak of what you''ve got there: properties.each do |property| it "is false if #{property} is different" do ... end end But alas, this pattern won''t work with shared example groups. I don''t know of any way to pass the "properties" variable in this case, and even if you could it wouldn''t work anyway because, AFAIK, the shared example group is itself only evaluated once when the file is first read. It isn''t re-evaluted each time it is included in another example group. At least, that''s my understanding of it. I might be wrong about that. So, looks like you''re stuck with having multiple assertions inside a single "it" block.> This is fine for a class, but the behaviour I want to prove with a Collection needs to be mixed in once per collection, eg (the last two are made up): > > describe User do > it_satisfies_contract "Entity Collection", for: "checklist_templates" > it_satisfies_contract "Entity Collection", for: "groups" > it_satisfies_contract "Entity Collection", for: "delegated_actions" > endHere the "standard" way of parametrizing this would be via blocks ("standard" in inverted commas because the ability to pass a block here is such a recent addition to RSpec). Cheers, Wincent
Ashley Moran
2010-Jul-26 16:15 UTC
[rspec-users] Formatting shared example descriptions with test data
On Jul 26, 2010, at 3:44 pm, Wincent Colaiuta wrote:> Personally I wouldn''t do this. It makes it harder for anybody coming to your project to understand what''s going on, because they see this "contract ''foo'' do" construct and don''t know what it is unless they dig into your spec_helper. If you really want the word contract to appear in there I would just write the shared examples like this:Actually, this doesn''t bother me. All the commercial development I do is pair-programmed, so it doesn''t take long for anyone new to a project to pick things like these up. I prefer to improve the semantics at the expense of some initial learning cost. (As it happens, this isn''t a commercial project anyway, it''s just something I''m doing on the side for my own benefit.)> So if I''m reading you correctly, this is where the "aspect of behavior that can occur N times" comes in, right? AFAIK the typical way to do this is via "macros" (ie. generating examples on the fly, typically keeping to examples of one assertion per iteration of the loop). So it''s just a minor tweak of what you''ve got there: > > properties.each do |property| > it "is false if #{property} is different" do > ... > end > endIf you mean by using a custom matcher in there, then yeah, I get you. But as this is more extensive, shared examples (which prove many individual pieces of behaviour) seemed a better fit than a matcher (which are intended to prove just one).> But alas, this pattern won''t work with shared example groups. I don''t know of any way to pass the "properties" variable in this case, and even if you could it wouldn''t work anyway because, AFAIK, the shared example group is itself only evaluated once when the file is first read. It isn''t re-evaluted each time it is included in another example group. At least, that''s my understanding of it. I might be wrong about that. So, looks like you''re stuck with having multiple assertions inside a single "it" block.Hmmm, if that''s the case, then this approach won''t work anyway. But I''ll investigate... it depends on the implementation. This is one of the things I want to find out.>> This is fine for a class, but the behaviour I want to prove with a Collection needs to be mixed in once per collection, eg (the last two are made up): >> >> describe User do >> it_satisfies_contract "Entity Collection", for: "checklist_templates" >> it_satisfies_contract "Entity Collection", for: "groups" >> it_satisfies_contract "Entity Collection", for: "delegated_actions" >> end > > Here the "standard" way of parametrizing this would be via blocks ("standard" in inverted commas because the ability to pass a block here is such a recent addition to RSpec).Yeah, I''m doing that to get the necessary objects inside the shared example. But as you pointed out, the scope inside example groups and inside the examples themselves are very different. Cheers Ash -- http://www.patchspace.co.uk/ http://www.linkedin.com/in/ashleymoran