Ruby constructor dependency injection... or not?
Dependency injection has proven to be something a black belt unit tester must know about if you are serious about unit testing. If you have written some sort of unit tests, would you be jealous if I tell you for some of us, running all these unit tests takes sub-1 second? In C# and Java, actively practising Dependency Injection makes mocking and stubbing out dependencies much easier, and thus tests become easy to write, and run fast because they do not need to make time consuming calls. In fact, constructor injection is one of my favourite design techniques:
public class Laptop
{
private IBattery battery;
public Laptop(IBattery battery)
{
this.battery = battery;
}
public void PowerOn()
{
if (battery.Meter >= 10)
{
// Booting Vista...
}
}
}
Then to unit test Laptop, you could use NMock like such:
[Test]
public void PowerOnFailsWhenBatteryIsTooLow()
{
mocks = new Mockery();
mockBattery = mocks.NewMock<IBattery>();
Expect.Once.On(mockBattery).Method("Meter").Will(Return.Value(9));
Laptop laptop = new Laptop(mockBattery);
laptop.PowerOn();
Assert.AreEqual("Off", laptop.PowerStatus);
mocks.VerifyAllExpectationsHaveBeenMet();
}
It may not be worth it to mock out Battery, but think about a lengthy web service class.
That's all true in C# and Java. Now in Ruby, I don't even need to constructor inject my Battery instance to unit test my Laptop class. I can just as easily unit test my Laptop class without having to inject my mock Battery:
class Laptop
def initialize
@battery = Battery.new
end
def power_on
if @battery.meter >= 10
# Booting Max OS X
end
end
def power_status
end
end
class LaptopTest < Test::Unit::TestCase
def test_power_on_fails_when_battery_too_low
Battery.any_instance.expects(:meter).returns(9)
laptop = Laptop.new
laptop.power_on
assert_equal :off, laptop.power_status
end
end
Basically I am mocking using Ruby Stubba/Mocha, but I don't even need to write an extra constructor to inject the Class Under Test's dependencies! With no interface IBattery, nothing! This is some cool trickery of programming in a dynamic language like Ruby, and I am discovering these things every day with my colleagues!
I know you are going to say "well, I can use reflection to do the same thing...", and I will tell you, sure, you try to do it in a readable manner and with one line of code. I didn't say you can't do it with C# or Java, I am just saying, this is how I can do it with one line of highly readable Ruby. Happy programming.
6 comments:
I think you'll find that Java can match the line count and readability of Ruby, at least in this instance ;)
http://digital-compulsion.blogspot.com/2006/09/java-mocking-without-injection-same.html
By "constructor injection" do you just mean knowing how to use the features of the language for what they are designed for?
Thanks for using and mentioning Mocha. Let me know if you have any feedback.
Hi!
Unfortunately, I am failing to see the "cool" point. You are doing this in ruby:
def initialize
@battery = Battery.new
end
but on Java/C# you want it injected, which is completely different. In both Java/C# you can do:
public Laptop() {
this.battery= new MyBattery();
}
If you are refering to the possibility to open Laptop class in Ruby, then somehow you can do the same in Java/C# test:
public class Dummy {
protected IInterface obj;
public Dummy() {
init();
}
protected void init() {
obj= new DefaultImpl();
};
public static void main(String[] args) {
Dummy dummy= new Dummy() {
protected void init() {
obj= new MockImpl();
}
};
}
public static interface IInterface {};
public static class DefaultImpl implements IInterface {};
public static class MockImpl implements IInterface {};
}
I agree it is a bit more difficult and less readable (... and can open some initialization problems); but I am still not sure what you meant in your post :-)
BR,
./alex
--
.w( the_mindstorm )p.
The point of my post is that you do not have to alter your constructor to accommodate tests due to dependencies anymore in Ruby. In your example, you do that by introducing a protected init() method, which is fine and you obviously do not have constructor injection. You also mentioned yourself that you are cluttering your code by doing so.
I am not arguing that this is something you can only accomplish in Ruby, but rather a nicer and much cleaner way of doing it in Ruby, compare doing the same thing in C# or Java, and therefore it excites me. I am sure you would agree, right?
When you do this:
def initialize
@battery = Battery.new
end
you don't have constructor injection either. So, what is the point? I think you missed some steps in your post, or I am completely missing the whole thing.
./alex
--
.w( the_mindstorm )p.
Post a Comment