Dry::Web::Container 通过多次调用解析产生不同的对象
Dry::Web::Container yielding different objects with multiple calls to resolve
我正在尝试编写一个测试来断言在成功 运行 时调用了所有已定义的操作。我在列表中定义了给定进程的操作,并从容器中解析它们,如下所示:
class ProcessController
def call(input)
operations.each { |o| container[o].(input) }
end
def operations
['operation1', 'operation2']
end
def container
My::Container # This is a Dry::Web::Container
end
end
那我测试如下:
RSpec.describe ProcessController do
let(:container) { My::Container }
it 'executes all operations' do
subject.operations.each do |op|
expect(container[op]).to receive(:call).and_call_original
end
expect(subject.(input)).to be_success
end
end
这失败了,因为从 ProcessController
内部调用 container[operation_name]
和从测试内部调用 container[operation_name]
会产生不同的操作实例。我可以通过比较对象 ID 来验证它。除此之外,我知道代码工作正常并且所有操作都被调用。
容器配置为自动注册这些操作,并在测试开始前完成运行。
如何解析同一个键 return 同一个项目?
TL;DR - https://dry-rb.org/gems/dry-system/test-mode/
您好,要获得您要求的行为,您需要在向容器注册项目时使用 memoize
选项。
请注意,Dry::Web::Container
继承了 Dry::System::Container
,其中包括 Dry::Container::Mixin
,因此尽管以下示例使用 dry-container
,但它仍然适用:
require 'bundler/inline'
gemfile(true) do
source 'https://rubygems.org'
gem 'dry-container'
end
class MyItem; end
class MyContainer
extend Dry::Container::Mixin
register(:item) { MyItem.new }
register(:memoized_item, memoize: true) { MyItem.new }
end
MyContainer[:item].object_id
# => 47171345299860
MyContainer[:item].object_id
# => 47171345290240
MyContainer[:memoized_item].object_id
# => 47171345277260
MyContainer[:memoized_item].object_id
# => 47171345277260
但是,要从 dry-web 执行此操作,您需要 memoize all objects auto-registered under the same path, or add the # auto_register: false
magic comment to the top of the files that define the dependencies and boot them manually.
记忆可能会导致并发问题,具体取决于您使用的是哪个应用程序服务器以及您的对象在请求生命周期中是否发生变化,因此 dry-container 的设计默认不记忆。
另一个可以说是更好的选择是使用 stubs:
# Extending above code
require 'dry/container/stub'
MyContainer.enable_stubs!
MyContainer.stub(:item, 'Some string')
MyContainer[:item]
# => "Some string"
旁注:
dry-system
提供了一个 injector 这样你就不需要在你的对象中手动调用容器,这样你的进程控制器就会变成这样:
class ProcessController
include My::Importer['operation1', 'operation2']
def call(input)
[operation1, operation2].each do |operation|
operation.(input)
end
end
end
我正在尝试编写一个测试来断言在成功 运行 时调用了所有已定义的操作。我在列表中定义了给定进程的操作,并从容器中解析它们,如下所示:
class ProcessController
def call(input)
operations.each { |o| container[o].(input) }
end
def operations
['operation1', 'operation2']
end
def container
My::Container # This is a Dry::Web::Container
end
end
那我测试如下:
RSpec.describe ProcessController do
let(:container) { My::Container }
it 'executes all operations' do
subject.operations.each do |op|
expect(container[op]).to receive(:call).and_call_original
end
expect(subject.(input)).to be_success
end
end
这失败了,因为从 ProcessController
内部调用 container[operation_name]
和从测试内部调用 container[operation_name]
会产生不同的操作实例。我可以通过比较对象 ID 来验证它。除此之外,我知道代码工作正常并且所有操作都被调用。
容器配置为自动注册这些操作,并在测试开始前完成运行。
如何解析同一个键 return 同一个项目?
TL;DR - https://dry-rb.org/gems/dry-system/test-mode/
您好,要获得您要求的行为,您需要在向容器注册项目时使用 memoize
选项。
请注意,Dry::Web::Container
继承了 Dry::System::Container
,其中包括 Dry::Container::Mixin
,因此尽管以下示例使用 dry-container
,但它仍然适用:
require 'bundler/inline'
gemfile(true) do
source 'https://rubygems.org'
gem 'dry-container'
end
class MyItem; end
class MyContainer
extend Dry::Container::Mixin
register(:item) { MyItem.new }
register(:memoized_item, memoize: true) { MyItem.new }
end
MyContainer[:item].object_id
# => 47171345299860
MyContainer[:item].object_id
# => 47171345290240
MyContainer[:memoized_item].object_id
# => 47171345277260
MyContainer[:memoized_item].object_id
# => 47171345277260
但是,要从 dry-web 执行此操作,您需要 memoize all objects auto-registered under the same path, or add the # auto_register: false
magic comment to the top of the files that define the dependencies and boot them manually.
记忆可能会导致并发问题,具体取决于您使用的是哪个应用程序服务器以及您的对象在请求生命周期中是否发生变化,因此 dry-container 的设计默认不记忆。
另一个可以说是更好的选择是使用 stubs:
# Extending above code
require 'dry/container/stub'
MyContainer.enable_stubs!
MyContainer.stub(:item, 'Some string')
MyContainer[:item]
# => "Some string"
旁注:
dry-system
提供了一个 injector 这样你就不需要在你的对象中手动调用容器,这样你的进程控制器就会变成这样:
class ProcessController
include My::Importer['operation1', 'operation2']
def call(input)
[operation1, operation2].each do |operation|
operation.(input)
end
end
end