Testing Sinatra application methods in isolation with RSpec

Sometimes you may want to test some Sinatra app methods in isolation (outside of a standard request flow). You probably even tried to do something like this:

require 'spec_helper'

describe App do
  subject { App.new }

  it 'expect run some specs here'
    expect(subject.current_user).to eq user
  end
end

Unfortunately you will end up with error like this:

NoMethodError: undefined method `current_user' for #<App app_file="/home/something/app.rb">
from (irb):2
from /home/.rvm/rubies/ruby-2.1.2/bin/irb:11:in `<main>'

This will occur, because Sinatra’s App new method creates a Sinatra::Wrapper, not an App instance. Of course everything will work as it should, if you follow the get/post/put/delete way of testing Sinatra apps:

require 'spec_helper'

describe App do
  before { get '/api/v1/credit_cards.json' }
    
  it { expect(last_response).to be_ok }
end

but it will fail when trying to invoke any App methods directly.

Luckily there’s a really simple solution to this issue. You just need to initialize it by manually allocating space and “bypassing” the new method:

require 'spec_helper'

describe App do
  subject do
    app = described_class.allocate
    app.send :initialize
    app
  end

  describe '#current_user' do
    before do
      expect(subject)
        .to receive(:params)
        .and_return(params)
        .at_least(:once)
    end

    context 'when there is no user_id' do
      let(:params) { {} }

      it { expect(subject.send :current_user).to be_nil }
    end
  end
end

That way you can get the “real” app instance (not a wrapper) that you can use as a subject in your specs. That way you can test your Sinatra app methods in isolation – without having to call a full request.

29
Jun 2014
POSTED BY
POSTED IN Ruby Software
DISCUSSION 5 Comments
  • http://sinatrarb.com Konstantin Haase

    Instead of

    app = described_class.allocate
    app.send :initialize

    You should be able to use

    app = described_class.new!

  • http://code.fellipebrito.com Fellipe Brito

    I’m working on it right now. 2 hours lost trying to do that and you saved my life. I have no idea who you are but you deserve a beer my friend.

  • Dmitriy Nesteryuik

    If it is difficult to test your code, then you need to rethink your decision about design. You use the trick which may not work with a new version of Ruby or Jruby. Do you want to spend time (money) on maintaining such code? Move logic of your application to own class and test it in isolation.

  • http://www.mensfeld.pl Maciej Mensfeld

    Sometimes you may want to test if an invocation of a method just delegates to somewhere else. This trick can help wit such case as well.

  • Ronny Ager-Wick

    any idea how to achieve the same result with minitest::spec? I’m having the exact same requirement using sinatra and minitest, but minitest doesn’t provide described_class, hence this solution doesn’t apply, unfortunately.