Инъекционные зависимости в приложении Sinatra

Я пишу приложение Sinatra, которое вызывает некоторые внешние службы. Я хочу, очевидно, мои тесты, чтобы избежать вызова реальных сервисов, так что предположим, что у меня есть этот

class MyApp < Sinatra::Base
  get '/my_method' do
    @result = ExternalServiceHandler.new.do_request
    haml :my_view
  end
end

И в моем тесте

describe "my app" do
  include Rack::Test::Methods
  def app() MyApp end

  it "should show OK if call to external service returned OK" do
    @external_service_handler = MiniTest::Mock.new
    @external_service_handler.expect :do_request, "OK"

    #Do the injection

    get '/my_method'
    response.html.must_include "OK"
  end

  it "should show KO if call to external service returned KO" do
    @external_service_handler = MiniTest::Mock.new
    @external_service_handler.expect :do_request, "KO"

    #Do the injection

    get '/my_method'
    response.html.must_include "KO"
  end

end

Я могу думать о двух способах введения этого. Я могу вызвать метод экземпляра или передать зависимость через конструктор. Так или иначе, поскольку стойка, похоже, не дает мне доступ к текущему экземпляру приложения, я нахожу это невозможным.

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

Есть ли способ сделать это?

Спасибо заранее.

Ответ 1

Мне удалось сделать это с помощью

describe "my app" do

  def app
    @INSTANCE
  end

  before do
    @INSTANCE ||= MyApp.new!
  end

  #tests here

end

Хотя мне особенно не нравится использование нового! перегрузка на момент его работы. Я могу использовать экземпляр, который будет использоваться с каждым тестом с помощью app.whatever_method

Ответ 2

Кажется, есть несколько вариантов. Вы можете либо передавать зависимости через конструктор, либо использовать настройки.

Конструктор Арг

class MyApp < Sinatra::Base
    def initialize(app = nil, service = ExternalServiceHandler.new)
        super(app)
        @service = service
    end

    get "/my_method" do
        @result = @service.do_request
        haml :my_view
    end
end

И в спецификации:

describe "my app" do
    include Rack::Test::Methods

    let(:app) { MyApp.new(service) }
    let(:service) { double(ExternalServiceHandler) }

    context "when the external service returns OK" do
        it "shows OK" do
            expect(service).to receive(:do_request).and_return("OK")

            get '/my_method'
            response.html.must_include "OK"
        end
    end

    context "when the external service returns KO" do
        it "shows KO" do
            expect(service).to receive(:do_request).and_return("KO")

            get '/my_method'
            response.html.must_include "KO"
        end
    end
end

Настройки

class MyApp < Sinatra::Base
    configure do
        set :service, ::ExternalServiceHandler.new
    end

    get "/my_method" do
        @result = settings.service.do_request
        haml :my_view
    end
end

И в спецификации:

describe "my app" do
    include Rack::Test::Methods

    let(:app) { MyApp.new }
    let(:service) { double(ExternalServiceHandler) }
    before do
        MyApp.set :service, service
    end

    context "when the external service returns OK" do
        it "shows OK" do
            expect(service).to receive(:do_request).and_return("OK")

            get '/my_method'
            response.html.must_include "OK"
        end
    end

    context "when the external service returns KO" do
        it "shows KO" do
            expect(service).to receive(:do_request).and_return("KO")

            get '/my_method'
            response.html.must_include "KO"
        end
    end
end