【问题标题】:Dry::Web::Container yielding different objects with multiple calls to resolveDry::Web::Container 通过多次调用来产生不同的对象来解析
【发布时间】:2019-03-13 15:28:44
【问题描述】:

我正在尝试编写一个测试来断言所有定义的操作都会在成功运行时被调用。我在列表中定义了给定进程的操作,并从容器中解析它们,如下所示:

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] 会产生不同的操作实例。我可以通过比较对象 ID 来验证它。除此之外,我知道代码工作正常,所有操作都被调用了。

容器配置为自动注册这些操作,并在测试开始运行之前完成。

如何使解析相同的键返回相同的项目?

【问题讨论】:

    标签: ruby dry-rb


    【解决方案1】:

    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
    

    但是,要从干网执行此操作,您需要 memoize all objects auto-registered under the same path,或将 # auto_register: false 魔术注释添加到定义依赖项和 boot them manually 的文件顶部。

    Memoizing 可能会导致并发问题,具体取决于您使用的应用服务器以及您的对象在请求生命周期中是否发生了变异,因此 Dry-container 的设计默认情况下不进行 memoize。

    另一个可以说是更好的选择是使用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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-10-18
      • 1970-01-01
      • 1970-01-01
      • 2019-09-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多