Dry::Web::Container, выдающий различные объекты с несколькими вызовами для разрешения

Я пытаюсь написать тест, чтобы подтвердить, что все определенные операции вызываются при успешном запуске. У меня есть операции для данного процесса, определенные в списке, и я разрешаю их из контейнера, например так:

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

Это не удается, потому что вызов container[operation_name] изнутри ProcessController и изнутри теста выдают различные экземпляры операций. Я могу это проверить, сравнив идентификаторы объектов. Кроме этого, я знаю, что код работает правильно и все операции вызываются.

Контейнер сконфигурирован для автоматической регистрации этих операций и был завершен до начала теста.

Как сделать так, чтобы при разрешении одного и того же ключа возвращался один и тот же элемент?

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

Однако, чтобы сделать это из Dry-Web, вам нужно либо запомнить все объекты, автоматически зарегистрированные по одному и тому же пути, либо добавить # auto_register: false Волшебный комментарий к началу файлов, которые определяют зависимости и загружают их вручную.

Создание мемоизаций может вызвать проблемы параллелизма в зависимости от того, какой сервер приложений вы используете, и от того, мутируют ли ваши объекты в течение жизненного цикла запроса, поэтому конструкция сухого контейнера не запоминается по умолчанию.

Другой, возможно, лучший вариант, это использовать заглушки:

# Extending above code
require 'dry/container/stub'
MyContainer.enable_stubs!
MyContainer.stub(:item, 'Some string')

MyContainer[:item]
# => "Some string"

Примечание:

dry-system предоставляет инжектор, так что вам не нужно вызывать контейнер вручную в ваших объектах, так что ваш контроллер процесса может выглядеть примерно так:

class ProcessController
  include My::Importer['operation1', 'operation2']

  def call(input)
    [operation1, operation2].each do |operation|
      operation.(input)
    end
  end
end
Другие вопросы по тегам