So I have a complex object that I need to construct. This complex
object, at runtime, takes an object in its initializer. The
initializer interrogates the object and creates several collaborative
objects based upon values from that interrogation.
The construction is complicated enough that it pretty much begs to be
implemented via the Builder Pattern.
My problem is that I don’t know how to test this behavior regardless
of whether the object handles its own construction or I create a
separate Builder object to construct it for me.
For example:
class ComplexObjectBuilder
def initialize @complex_object = ComplexObject.new
end
def create_foo(control, value)
case control do
when :a then @complex_object.foo = SpecialObject.new(value * 4)
when :v then @complex_object.foo = OtherObject.new
when :z then @complex_object.foo = YAObject.new
end
end
def create_bar(control, value)
# similar to above
end
def construct
# some business rules to make sure the object is complete; use
# defaults for fields that were not set via the interface
return @complex_object
end
end
How the heck do I test anything here? I do not see how I can validate
the behavior of #create_foo or #create_bar without exposing @complex_object via a public interface. Those #create_* methods are
purely for construction and do not return a value. Only #construct
returns a value which should be the completed object
Is there a transformation to consider to make this more testable?
I’m hoping someone has tackled this before and can provide me some
insight. Many thanks…
How the heck do I test anything here? I do not see how I can validate
the behavior of #create_foo or #create_bar without exposing @complex_object via a public interface. Those #create_* methods are
purely for construction and do not return a value. Only #construct
returns a value which should be the completed object
Is there a transformation to consider to make this more testable?
Just to show I’m not sitting back and waiting for the answer to be
handed to me on a silver platter…
I looked through the rspec specs and found a construction that might
be useful.
In spec/spec/extensions/main_spec.rb we see this:
it “should create an Options object” do @main.send(:rspec_options).should
be_instance_of(Spec::Options) @main.send(:rspec_options).should === $rspec_options
end
In a #before block the @main instance variable was instantiated and a
module was included. I’m assuming #send is used to check the value of
a private accessor.
So I could potentially do something like this:
describe ComplexObjectBuilder, “construction” do
before(:each) do @builder = ComplexObjectBuilder.new @builder.foo(control1, val1) @builder.bar(control2, val2)
end
it “should create a YAObject for #foo” do @builder.send(:foo).should be_instance_of(YAObject)
end
it “should create a ZObject for #bar” do @builder.send(:bar).should be_instance_of(ZObject)
end
end
Is this acceptable? I read through the “specs on private methods”
thread and the consensus was this should only be done when there is no other option. Is there another option?
Is this acceptable? I read through the “specs on private methods”
thread and the consensus was this should only be done when there is no other option. Is there another option?
There’s always another option, and anybody who says otherwise is flat
out wrong. You can always test an object via its public interface.
If there’s a private method that you want to test, but there’s no way
to exercise it by the public interface, then that means the code is
not used and should be deleted.
My problem is that I don’t know how to test this behavior regardless
def create_foo(control, value)
baz = @builder.construct
I’m hoping someone has tackled this before and can provide me some
before(:each) do
end
end
Pat,
thanks for your response.
I have question regarding your suggestion. In the first “it” you are
asserting that @built.foo should be an object. The @built instance
variable is the ComplexObject and not the ComplexObjectBuilder.
Shouldn’t we be testing the behavior of the builder and not the
ComplexObject the builder produces? Another way, shouldn’t I put the
ComplexObject assertions into its own set of specs?
It just seems like we’re mixing the behavior of two objects here:
ComplexObjectBuilder and ComplexObject. Perhaps that is okay since
they are so tightly coupled. In a sense the ComplexObjectBuilder is
just a fancy constructor for the other object; it only gets refactored
out to its own object because it has so many parts/steps.
separate Builder object to construct it for me.
when :a then @complex_object.foo = SpecialObject.new(value * 4)
# some business rules to make sure the object is complete; use
How the heck do I test anything here? I do not see how I can validate
cr
Hey Chuck,
I would toss whatever code you have and BDD it from scratch.
Shouldn’t be that difficult…you can start off with stuff like
describe ComplexObjectBuilder, " when foo is built with :a" do
before(:each) do
builder = ComplexObjectBuilder.new
builder.create_foo :a, 3 @built = builder.construct
end
it “should create a special object” do @built.foo.should be_a(SpecialObject)
end
it “should quadruple the value passed to the special object” do @built.foo.some_val.should == 12
end
end
Alternatively, you could mock the calls to the constructed objects.
In this case though, that probably leads to specs that are too tightly
coupled to the implementation.
I know this seems like kind of a cop-out, but you’ll get much better
results if you BDD this from scratch. If this is super complex legacy
stuff, just try to get some semi-high level tests around it (the sort
of test I showed would work fine), and then you can refactor it.
Pat
This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.