On 11/2/2010 3:01 PM, Manuel Kiessling wrote:
> Hello everyone,
> 
> I'm quite new to Ruby, with a strong background in PHP.
> 
> I had a great time finding my way around in Ruby, because it is a fantastic language. However, right now I'm a bit stuck regarding the issue of loose coupling / inversion of control / writing testable units.
> 
> I spent the last days reading a lot of articles, but feel sort of puzzled now.
> 
> As far as I can see, there are several approaches. I will start with my own, which probably is the weirdest of all - feel free to dissect it.
> 
> Let's say I have 2 classes, SomeClass and OtherClass, and SomeClass depends on OtherClass, because in some cases it needs to create an instance of this class, and in others it doesn't, which is why we can't simply pass an already created instance of OtherClass into SomeClass.
> 
> This is how it could be done using no DI at all:
> 
> class SomeClass
>   def do_something(a, b)
>     c = a + b
>     if (c > 5) then
>       o = OtherClass.new()
>       o.log(c)
>     end
>   end
> end
> 
> s = SomeClass.new
> s.do_something(1, 2)
> 
> Which of course doesn't allow me to replace "o" with a mock object for testing etc.
> 
> 
> This is how it might be done using the fact that ruby classes are objects (my approach):
> 
> class SomeClass
>   def do_something(a, b, logObjectClass)
>     c = a + b
>     if (c > 5) then
>       o = logObjectClass.new()
>       o.log(c)
>     end
>   end
> end
> 
> s = SomeClass.new
> s.do_something(1, 2, OtherClass)
> 
> This way, I can inject into SomeClass which class to actually use - as long as whatever I pass using logObjectClass supports the expected protocol, all should be fine. I could easily inject a mock log object for my tests.
> 
> But, I couldn't find any articles recommending this approach, and I guess there's a reason for that...
> 
> 
> Another solution which seems to be more along "the Ruby way" is this:
> 
> class SomeClass
>   def do_something(a, b)
>     c = a + b
>     if (c > 5) then
>       o = get_logger()
>       o.log(c)
>     end
>   end
> 
>   def get_logger()
>     OtherClass.new()
>   end
> end
> 
> // Replacing with mock object:
> s = SomeClass.new
> def s.get_logger()
>   MockLoggerClass.new()
> end
> s.do_something(1, 2)

What I'm about to suggest will generate warnings from Ruby and is
probably not the "best" way, but you could also replace OtherClass with
your MockLoggerClass with a simple assignment during your test:

OtherClass = MockLoggerClass  # This generates a warning...
s = SomeClass.new
s.do_something(1, 2)


Ruby will warn you about replacing an already initialized constant,
namely OtherClass, but it should get the job done with a minimum of fuss
on your part.

One more option would be to override OtherClass.new and make it simply
call MockLoggerClass.new with the same arguments:

def OtherClass.new(*args)
  MockLoggerClass.new(*args)
end

s = SomeClass.new
s.do_something(1, 2)


Among all the solutions, I don't know the best way, but I would probably
go with overriding OtherClass.new.  Your second proposed solution isn't
bad either, but I wouldn't want to go that way just to make testing
easier/possible.  I would need a good functional reason to break up the
code that way.  Of course, division of labor in your implementation
isn't a bad reason. ;-)

-Jeremy