The problem with dependency injection

Dependency injection
If you use dependency injection as a default get-go tool, you might get intimidated by the fact that you need to construct dependencies each time you want to call a given class.
Let me pick apart an example, where you need to receive an input, parse it and send it to a certain third party provider:

class Notifier
    def initialize(input, parser, gateway)
        @input = input
        @parser = parser
        @gateway = gateway
    end
    
    def call
        parsed_input = parser.parse(input)
        gateway.call(parsed_input)
    end
    
    attr_reader :parser, :gateway
end

Now, if we do it like this, obviously the construction of this class would look like so:

parser = Parser.new
gateway = Gateway.new
input = "SOME INPUT FORM WHATEVER"

Notifier.new(input, parser, gateway).call

If you have to deal with something like rails, there is a big chance that you want to keep your controllers free of all this code. Let me present you with some techniques I use to deal with this issues.

.build method

One of the simplest ways to solve this is to define a simple build method that defines the defaults. It's actually pretty straight forwards, so let's dig into the code:

class Notifier

    class << self
        def build(input)
            new(
                input,
                Parser.new,
                Gateway.new
            )
        end
    end
    
    def initialize(input, parser, gateway)
        @input = input
        @parser = parser
        @gateway = gateway
    end
    
    def call
        parsed_input = parser.parse(input)
        gateway.call(parsed_input)
    end
    
    attr_reader :parser, :gateway, :input
end

In this example, we are simply assigning all of our dependencies in build method. This is quite an okayish solution, but it requires for us to have yet another method and I want to avoid writing the code as much as possible. I use .build method whenever I need to do some additional work on arguments, before passing them to the constructor.

We have achieved a cleaner interface of the notifier class:

input = "SOME INPUT FORM WHATEVER"

Notifier.build(input).call

I think it is quite nice and acceptable.

Using default arguments

This method is more straightforward than the previous one. If I use this approach, I heavily leverage the keyword arguments for Ruby 2.

class Notifier
    def initialize(input:, parser: Parser.new, gateway: Gateway.new)
        @input = input
        @parser = parser
        @gateway = gateway
    end
    
    def call
        parsed_input = parser.parse(input)
        gateway.call(parsed_input)
    end
    
    attr_reader :parser, :gateway, :input
end

This approach eliminates the need for a class method and gives us much more tidy class. One side note here is the fact that the initialize method argument list tends to get long if you have namespaced class names. If you are okay with that (I am), then use this method.

With this approach we achieved the following result:

input = "SOME INPUT FORM WHATEVER"

Notifier.new(input: input).call

Why is this good?

I write code and I do it a lot. I want my codebase to be maintainable and testable. First of all, dependency injection just eliminates the need for calling "real" objects. Let's imagine, that sending a message to the gateway is quite expensive HTTP call. If you want to mock it, it's quite simple to do and pass the double as a dependency.

describe Notifier do
    it "sends a message to a gateway" do
        gateway = instance_double(Gateway)
        expect(gateway).to receive(:call)
        input = "My random input again"
        Notifier.new(input: input, gateway: gateway)
    end
end

I really like the new interface checking capabilities of spec doubles. They validate that an instance of a particular class can receive a message and it saves a lot of troubles with outdated doubles.

Usually at the end of each test, I will have an integration test, that touches the whole chain of dependencies, otherwise mocking is not safe, even with the interface checking that RSpec provides.

As a side note, it is generally a good idea to mock only the things you own. If it's a third party library, create a wrapper around it, that has an interface acceptable to you.

In "the real world" code tends to get little more complex than the examples above, but with the little bit of imagination, it is pretty easy to figure that stuff out.

Some of these ideas are taken from Sandi Metz's book "POODR". I can't recommend it enough.

Happy injecting!

Janis Miezitis

Read more posts by this author.

Subscribe to Janis Miezitis personal blog

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!