On Nov 2, 2010, at 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.

Actually, all of the Ruby TDD or BDD frameworks include mocking frameworks that support this. Here's what it might look like in rspec:

it "should create instance of OtherClass" do
  o = mock('other class')
  OtherClass.should_receive(:new).and_return(o)

  SomeClass.new.do_something(1, 2)
end

It's a valid technique and gets used quite a bit.


> 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...

It's not a bad idea and will work just fine. I have used this technique a couple of times. What I usually do is make that 3rd argument a default parameter so that by default it uses the class that I hardcode but I am free to override it (during testing) with a mock of my own.

e.g.

def do_something(a, b, klass = OtherClass)
  c = a + b
  if c > 5
    o = klass.new
  end
end

SomeClass.new.do_something(1, 2, MockClass) # inject my own
SomeClass.new.do_something(1, 2) # will use OtherClass by default


> 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)
> 
> Could someone help me to understand which approach is better, and why? Or if maybe I'm getting it completely wrong?

I rarely see code structured like this. I wouldn't do it this way particularly since you have two better solutions that you listed above. 

Ultimately, Ruby is flexible enough that you can accomplish your goal *cleanly* using multiple different techniques. I would use the ones you listed in the order that you listed them (best is first, etc).

Welcome to Ruby!

cr