
Lately,
I’ve been toying around with native Android application development using Java.
It got me hooked on using RoboGuice and Google Guice. Coming from a dynamic language background, it got me thinking as to why I never felt the need for a dependency injection framework before. Guess what? Ruby and Python both come with their own built-in dependency injection framework! Heresy?
Well let’s check out this small example and see if it’s the case or not.
Let’s say you have a small application that allows you to display all the books present in your database.
Let’s say your Book class looks a little something like this :
class Book
attr_reader :title, :author
def initialize(title, author)
@title = title
@author = author
end
end
and that the class that allows you to visualize your Books looks a little something like this :
class VisualBookListing
def initialize(booksCollection = RubyBooksCollection)
@books_collection = booksCollection.new
end
def display
@books_collection.get_all_books.each{ |book|
puts “%s written by : %s ” %[book.author, book.title]
}
end
end
By using RubyBooksCollection as the default parameter value, it becomes the default implementation of the BooksCollection your BookListing’s class is expecting to receive.
This would take the place of the following line you would often see in a Google Guice module
bind(BooksCollection.class).to(RubyBooksCollection.class);
Although, unlike in Java, you do not have the advantages and disadvantages associated with the declaration of your interface.
Unfortunately, the default implementation of our RubyBooksCollection is this :
class RubyBooksCollection
def get_all_books
return Database.get_ruby_books
end
end
When we’ll want to write a unit test for our VisualBookListing, this means we’ll have to setup a database. Our test is now an integrated instead of being a unit test.
Our VisualBooksListing could not care less whether or not our data comes from a database, a text file or holy intervention.
Plus we get the hassle of setting up a database with the added bonus of making our test slower.
What to do then ? One solution could be to write a mocked BooksCollection to use instead of our RubyBooksCollection. It should be pretty easy to pass around since the dependency is injected through the constructor.
How could we do this ? You could use one of the many awesome mocking framework. Here’s an example I came up with without the use of any framework :
class MockedBooksCollection
def get_all_books
growing_object_oriented_software = Book.new "Growing OO Software", "Steve Freeman"
domain_driven_design = Book.new "Domain Driven Design", "Eric Evans"
return [growing_object_oriented_software, domain_driven_design]
end
end
In your test, you would simply do assertions based on the result of this call to the VisualBookListing :
testBookListingDisplay = VisualBookListing.new library = MockedBooksCollection
testBookListingDisplay.display
whereas in prod, you would do a little something like this
productionBookListing = VisualBookListing.new
productionBookListing.display
This make it a little simpler than with Guice, where you would have to create a new context for your tests**.
So there you go, dependency injection in Ruby, without the need for a framework, and all the added benefits.
-Nicholas Lemay of team Sosoft.
**If you are interested, there is a nice little library called MycilaTesting written by Mathieu Carbou that facilitates testing context creation. The syntax of the tests produced look a little like this :
@GuiceContext(LibraryTests.LibraryTestsModule.class)
public class LibraryTests extends MycilaJunit4Test{
@Inject SomeLibraryDependency someLibraryDependency;
//You write your tests here
public static final class LibraryTestsModule extends AbstractModule {
protected void configure() {
bind(SomeLibraryDependency.class).to(MockedLibraryDependency.class);
}
}
}