Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Injecting dependencies in Sinatra app

Tags:

ruby

sinatra

rack

I'm writing a Sinatra app that calls some external services. I want obviously my tests to avoid calling the real services so suppose at the moment I have this

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

And in my test

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

I can think of two ways to inject this. I can call an instance method or passing the dependency through constructor. Anyway since rack does not seem to give me access to current application instance I'm finding this impossible.

I can declare a class method for this but I'd prefer working with instances if possible. To keep potentially possible to have different injections in each case and avoiding global state that could harm other tests if I forget to rollback state.

Is there any way to accomplish this?

Thanks in advance.

like image 447
Rafa de Castro Avatar asked Sep 07 '12 12:09

Rafa de Castro


2 Answers

It seems as though there are a couple of options. You can either pass the dependencies in through the constructor, or use settings.

Constructor Args

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

And in the spec:

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

Settings

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

And in the spec:

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
like image 158
Daniel Avatar answered Nov 02 '22 23:11

Daniel


I finally managed to do this with

describe "my app" do

  def app
    @INSTANCE
  end

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

  #tests here

end

Although I don't particularly like using the new! overloading at the moment it's working. I can use the instance that will be used with each test with app.whatever_method

like image 34
Rafa de Castro Avatar answered Nov 03 '22 00:11

Rafa de Castro