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

Quoting Patrick J. Collins [email protected]:

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?

On Nov 14, 2011, at 6:57 PM, Patrick J. Collins wrote:

args.shift.to_s + args.flatten.map {|component| “[#{component}]”}.join
end

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

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

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

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

On Nov 14, 2011, at 11:59 PM, Patrick J. Collins wrote:

But what happens if the classes do not have the same argument expectations,
end
I 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

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