Unit testing with mock objects


#1

I’ve been writing an extensive set of unit tests for a bunch of code
that I’ve been developing over the past couple of years to improve its
maintainability and have in the process needed to make more than a few
mock objects to encapsulate functionality like databases, web
services, distributed objects, and peripherals to which the program
interfaces. I’ve been wondering what is the best way to refactor these
kinds of classes so that it becomes easy to inject mock objects where
these domain objects are required. What I’ve been doing so far is to
attach a block to the initialize method that, when specified, is
called to instantiate these mock objects, e.g.:

class Foo
def initialize(x, y, &block)
if block_given?
dobjs = block.call
@domain_obj1 = dobjs[:domain_obj1]
@domain_obj2 = dobjs[:domain_obj2]
else
@domain_obj1 = DomainObj1.new
@domain_obj2 = DomainObj2.new
end
end

so that in my testing code I can do the following:

FlexMock.use(“domain_obj1”) do |dobj1|
FlexMock.use(“domain_obj2”).do |dobj2|
f = Foo.new(x, y) { {:domain_obj1 => dobj1, :domain_obj2 => dobj2 }
}
end
end

but this hardly feels like the “right” way to do it. It feels like
such a kludge. I know I could write factory methods to create my
domain objects and then reopen the class at testing time and rewrite
the factory methods to return the mock objects, but then it’s not
exactly clear how I could feed these rewritten factory methods with
mock objects instantiated as above, from within the test. I could
rewrite the initialization method to accept instances of my domain
objects instead so that at test time I could just feed it with my mock
objects, but that would also require me to rewrite all the code that
uses instances of the class in question as well, and that’s not such a
good idea to do everywhere. Any suggestions?


#2

On 5/11/06, Dido S. removed_email_address@domain.invalid wrote:

end

so that in my testing code I can do the following:

FlexMock.use(“domain_obj1”) do |dobj1|
FlexMock.use(“domain_obj2”).do |dobj2|
f = Foo.new(x, y) { {:domain_obj1 => dobj1, :domain_obj2 => dobj2 } }
end
end

I like the “trailing option hash” “pattern” (used loosely). It’s
pretty close to what you’ve got, but syntactlcally a little nicer. I’d
do it like this for your demonstrated class:

class Foo
DEFAULTS = {
:domain_obj1 => DomainObj1,
:domain_obj2 => DomainObj2
}

def initialize(x, y, options={})
  options.merge!(DEFAULTS)
  @domain_obj1 = options[:domain_obj1]
  @domain_obj2 = options[:domain_obj2]
  @domain_obj1 = @domain_obj1.new if Class === @domain_obj1
  @domain_obj2 = @domain_obj2.new if Class === @domain_obj2
end

end

I use the classes in the DEFAULTS array so that each instance of Foo
can have it’s own DomainObj1 (in the default case), rather than having
a reference to the same object that was instantiated when the hash was
created. The “x = x.new if Class === x” “pattern” (again, loosely
using the P word) is also useful in that you can pass in a MockClass
as well, rather than an instantiated object, if you want. Example
usage:

passing mock objects

FlexMock.use(“domain_obj1”) do |dobj1|
FlexMock.use(“domain_obj2”).do |dobj2|
f = Foo.new(x, y,
:domain_obj1 => dobj1,
:domain_obj2 => dobj2)
end
end

–OR–

passing mock classes

f = Foo.new(x, y,
:domain_obj1 => MockDomainObj1,
:domain_obj2 => MockDomainObj2)

–OR–

normal, non-test usage, should look same as before:

f = Foo.new(x, y)

How’s that look?

Jacob F.


#3

On 5/12/06, Jacob F. removed_email_address@domain.invalid wrote:

def initialize(x, y, options={})
  options.merge!(DEFAULTS)
  @domain_obj1 = options[:domain_obj1]
  @domain_obj2 = options[:domain_obj2]
  @domain_obj1 = @domain_obj1.new if Class === @domain_obj1
  @domain_obj2 = @domain_obj2.new if Class === @domain_obj2
end

end

Shame on me for posting without testing… I got my merge backwards,
it should be:

options = DEFAULTS.merge(options)

Since the values in the argument, not receiver, take precedence if
both have a key.

Jacob F.