Forum: RSpec need some advice on the best way to structure testable shared methods

Posted by Patrick Collins (patrick99e99)
on 2011-11-15 03:05
(Received via mailing list)
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
Posted by Cynthia Kiser (Guest)
on 2011-11-15 03:31
(Received via mailing list)
Quoting Patrick J. Collins <patrick@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?
Posted by Patrick Collins (patrick99e99)
on 2011-11-15 04:11
(Received via mailing list)
> 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
Posted by David Chelimsky (Guest)
on 2011-11-15 05:17
(Received via mailing list)
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
Posted by Patrick Collins (patrick99e99)
on 2011-11-15 07:19
(Received via mailing list)
> 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
Posted by David Chelimsky (Guest)
on 2011-11-15 13:24
(Received via mailing list)
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
Posted by Pat Maddox (Guest)
on 2011-11-16 04:07
(Received via mailing list)
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/ex... 
for some examples.

Pat
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.