This seems to come up often because of how dry-container encourages DI so seamlessly, but I’m frequently running into this pattern, and I’m wondering if there might be a tool to make it simpler. First a slightly contrived code example of what I’m talking about:
class MyClient
include MyContainer::Import[:http]
def initialize(credentials:, **deps)
super(**deps)
@auth_http = http.basic_auth(credentials.user, credentials.password)
end
end
class MyAdapter
include MyContainer::Import[:client]
def initialize(model: model, **deps)
super(**deps)
@client = client.new(credentials: model.credentials)
end
end
class MyCommand
include MyContainer::Import[:adapter]
def call(a, b, c)
model = Model.find_by_a(a)
adapter = adapter.new(model: model)
adapter.do_something(b)
adapter.do_something_else(c)
end
end
I like this structure of code, because the Client only has to care about “credentials”, the Adapter works on getting the credentials from the Model, and the Commands just pass the Model to Adapter.
However, the tension lies in testing this mess. I want to “pyramid/integration-test” the whole thing, to ensure changes to the Adapter doesn’t interfere with the Commands, and its easier to just have shared “fake client” between all the tests. However, because what I’m injecting is classes instead of instances, what I struggle with is how to inject the right thing at the right time. I end up having the mock both the class and the instance:
let(:client_class) { class_double(MyClient, new: client) }
let(:client) { instance_double(MyClient, mocks...) }
before { MyContainer.stub(client: client_class) }
That client_class
double feels like a smell, but I’m not quite sure what to do about it. Times like this, I really wish ruby had partial function application, so I could do something like this:
client = MyClient.inject(http: fake_http) # Returns a "partial class" with the
adapter = MyAdapter.inject(client: client) # dependency injected but not initialized
command = MyCommand.new(adapter: adapter)
command.call(1, 2, 3) # This finally calls `.new` on all those "partial classes"
This seems like something up the Dry.rb team’s alley, so I thought this would be as good a place as any to ask. Has anyone come across anything like this, or tools/patterns that might help?