Patrick J. Collins
2011-Nov-15 00:57 UTC
[rspec-users] need some advice on the best way to structure testable shared methods
So, I recognize that there are many different ways to accomplish what I am trying to do, but none of them seem to be as elegant as I would like. I have a few classes which need to have shared functionality... So I could do something like this: module NameBuilder def build_name(*args) args.shift.to_s + args.flatten.map {|component| "[#{component}]"}.join end end class Foo include NameBuilder def initialize(arg1, arg2) ... end end class Bar include NameBuilder def initialize(arg1, arg2) ... end end ... But, then I need to do something like: shared_examples "a nameable thingie" do |obj| describe "#build_name" do it "it adds the collection of arguments to the base components and formats them for a form element name attribute" do obj.build_name(:lol, :lollerskates, :roflcopter).should == "lol[lollerskates][roflcopter]" end end end describe Foo do it_behaves_like "a nameable thingie", Foo.new(nil, nil) end describe Bar do it_behaves_like "a nameable thingie", Bar.new(nil, nil) end Which I don''t like, mainly because Foo & Bar''s initialize methods require arguments, and I am having to do (nil, nil) which seems very uncool... ........... So, another approach would be to do: class Nameable def build_name(*args) args.shift.to_s + args.flatten.map {|component| "[#{component}]"}.join end end ... class Foo < Nameable ... end class Bar < Nameable ... end And then I can have a dedicated spec for Nameable and not worry about testing that in Foo and Bar... But, I am not 100% crazy about that approach either. Can anyone suggest a better way? Thanks! Patrick J. Collins http://collinatorstudios.com
Cynthia Kiser
2011-Nov-15 02:22 UTC
[rspec-users] need some advice on the best way to structure testable shared methods
Quoting Patrick J. Collins <patrick at collinatorstudios.com>:> describe Bar do > it_behaves_like "a nameable thingie", Bar.new(nil, nil) > end > > Which I don''t like, mainly because Foo & Bar''s initialize methods require > arguments, and I am having to do (nil, nil) which seems very uncool...Having trouble following your example. Why do you have to pass Bar.new(nil, nil) for this test? -- Cynthia N. Kiser cnk at ugcs.caltech.edu
Patrick J. Collins
2011-Nov-15 02:42 UTC
[rspec-users] need some advice on the best way to structure testable shared methods
> Having trouble following your example. Why do you have to pass > Bar.new(nil, nil) for this test?Because the shared example is testing the method #build_name from the module that is included in the various classes-- it needs the object that #build_name is attached to in order to test the functionality. The .new(nil, nil) is because the initialize method of the classes that include the module have expected arguments... I supposed I could have done def initialize(arg1 = nil, arg2 = nil) to prevent having to do Bar.new(nil, nil) in the test, but I actually would rather the init method have actual argument expectations. Patrick J. Collins http://collinatorstudios.com
David Chelimsky
2011-Nov-15 03:15 UTC
[rspec-users] need some advice on the best way to structure testable shared methods
On Nov 14, 2011, at 6:57 PM, Patrick J. Collins wrote:> So, I recognize that there are many different ways to accomplish what I am > trying to do, but none of them seem to be as elegant as I would like. > > I have a few classes which need to have shared functionality... > > So I could do something like this: > > module NameBuilder > > def build_name(*args) > args.shift.to_s + args.flatten.map {|component| "[#{component}]"}.join > end > > end > > class Foo > include NameBuilder > > def initialize(arg1, arg2) > ... > end > end > > class Bar > include NameBuilder > > def initialize(arg1, arg2) > ... > end > end > > ... > > But, then I need to do something like: > > shared_examples "a nameable thingie" do |obj| > > describe "#build_name" do > > it "it adds the collection of arguments to the base components and formats them for a form element name attribute" do > obj.build_name(:lol, :lollerskates, :roflcopter).should == "lol[lollerskates][roflcopter]" > end > > end > > end > > describe Foo do > it_behaves_like "a nameable thingie", Foo.new(nil, nil) > end > > describe Bar do > it_behaves_like "a nameable thingie", Bar.new(nil, nil) > end > > Which I don''t like, mainly because Foo & Bar''s initialize methods require > arguments, and I am having to do (nil, nil) which seems very uncool...Use the described class: shared_examples "a nameable thingie" do |klass| describe "#build_name" do it "it adds the collection of arguments to the base components and formats them for a form element name attribute" do described_class.new(nil,nil).build_name(:lol, :lollerskates, :roflcopter).should == "lol[lollerskates][roflcopter]" end end end describe Foo do it_behaves_like "a nameable thingie" end describe Bar do it_behaves_like "a nameable thingie" end Cheers, David
Patrick J. Collins
2011-Nov-15 05:59 UTC
[rspec-users] need some advice on the best way to structure testable shared methods
> Use the described class: > > shared_examples "a nameable thingie" do |klass| > describe "#build_name" do > it "it adds the collection of arguments to the base components and formats them for a form element name attribute" do > described_class.new(nil,nil).build_name(:lol, :lollerskates, :roflcopter).should == "lol[lollerskates][roflcopter]" > end > end > endBut what happens if the classes do not have the same argument expectations, such as: class Foo include NameBuilder def initialize(arg1) ... end end class Bar include NameBuilder def initialize(arg1, arg2, arg3) ... end end ... In this case, "described_class.new(nil, nil)" is not going to work out very well. Patrick J. Collins http://collinatorstudios.com
David Chelimsky
2011-Nov-15 12:18 UTC
[rspec-users] need some advice on the best way to structure testable shared methods
On Nov 14, 2011, at 11:59 PM, Patrick J. Collins wrote:>> Use the described class: >> >> shared_examples "a nameable thingie" do |klass| >> describe "#build_name" do >> it "it adds the collection of arguments to the base components and formats them for a form element name attribute" do >> described_class.new(nil,nil).build_name(:lol, :lollerskates, :roflcopter).should == "lol[lollerskates][roflcopter]" >> end >> end >> end > > But what happens if the classes do not have the same argument expectations, > such as: > > class Foo > > include NameBuilder > > def initialize(arg1) > ... > end > > endI misunderstood your initial example, thinking that NameBuilder required an initializer w/ two args. I''d use a factory pattern here: def new_foo(arg1=nil,arg2=nil) Foo.new(arg1,arg2) end def new_bar(arg=nil) Bar.new(arg) end Now you can use these without arguments when you want to: describe Foo do it_behaves_like "a nameable thingie", new_foo end describe Bar do it_behaves_like "a nameable thingie", new_bar end And you can use the with arguments as well: it "does something else" do foo = make_foo(x) other_foo = make_foo(x,y) end David
Pat Maddox
2011-Nov-16 01:44 UTC
[rspec-users] need some advice on the best way to structure testable shared methods
On Nov 14, 2011, at 4:57 PM, Patrick J. Collins wrote:> > Can anyone suggest a better way?Really tough to follow that example, so apologies if I''m off. I use the template method pattern for stuff like this. My shared example group references a method that isn''t implemented. Example groups that use that shared example group then define it using let. And of course you can parameterize the shared example groups or pass it a context. See https://www.relishapp.com/rspec/rspec-core/docs/example-groups/shared-examples for some examples. Pat