Dependency injection - Tier3/bosh GitHub Wiki
Dependency injection, Ruby style
The goal is to allow objects to acquire dependencies while still preserving the ability for tests to substitute mock implementations. Plus the test harness needs to be able to "reset the world" and replace application-scoped objects in between test runs.
In the past, Director relied on the Config
object to maintain all application-scoped state. Migrating to DI, we use the App
object to maintain all application-scoped state (preferably in decomposed objects). Follow this pattern to initialize an object with dependencies:
class Something
def initialize(mandatory_param, options={})
@mandatory_param = mandatory_param
@dependent_scoped_thing = options.fetch(:cheap_thing) { CheapThing.new }
@application_scoped_thing = options.fetch(:expensive_thing) { App.instance.thing_service.thing }
end
end
Initializers should have a hash param which allows for dependencies (and possibly other options) to be specified as named options. In normal application use, these dependencies are not specified in the new
call and their lazily-evaluated fetch()
values are used instead. In unit tests, mocks can be passed in, presenting a nice set of let
statements instead of a series of ugly stubs on class methods.
The App
object serves as a repository of application scoped objects. It can be reset between tests by creating a new App object; each App.new
resets the singleton instance
.