Search code examples
rubyrspecsinatrarack

Raising 500 errors deliberately in Sinatra in order to test how they are handled


I want to write an RSpec test which verifies that, should a 500 error occur in my Sinatra-powered API, the error will be caught by a Sinatra error definition and returned to the client in a JSON format. That is, rather than returning some HTML error page, it returns JSON like this to conform with the rest of the API:

{
  success: "false",
  response: "Internal server error"
}

However, I'm unsure how to actually trigger a 500 error in my Sinatra app in order to test this behaviour with RSpec. I can't find a way to mock Sinatra routes, so currently my best idea is this route which deliberately causes a 500. This feels like a pretty dreadful solution:

get '/api/v1/testing/internal-server-error' do
  1 / 0
end

Is there a way to mock Sinatra routes so that I can have, say, /'s route handler block raise an exception, therefore triggering a 500? If not, is there some other way to deliberately cause a 500 error in my app?


Solution

  • When facing a situation like this, what I usually do is separate concerns, and move logic outside of the Sinatra get ... block. Then, it is easy to stub it and make it raise an error.

    For example, given this server code:

    # server.rb
    require 'sinatra'
    
    class SomeModel
      def self.some_action
        "do what you need to do"
      end
    end
    
    get '/' do
      SomeModel.some_action
    end
    

    You can then use this code to have the model, or any other class/function you are using to actually generate the response, raise an error, using this spec:

    # spec
    describe '/' do
      context 'on error' do
        before do 
          allow(SomeModel).to receive(:some_action) { raise ArgumentError }
        end
    
        it 'errors gracefully' do
          get '/'
          expect(last_response.status).to eq 500
        end
      end
    end
    

    For completeness, here is a self contained file that can be tested to demonstrate this approach by running rspec thisfile.rb:

    # thisfile.rb
    require 'rack/test'
    require 'rspec'
    require 'sinatra'
    
    # server
    
    class SomeModel
      def self.some_action
        "do what you need to do"
      end
    end
    
    get '/' do
      SomeModel.some_action
    end
    
    # spec_helper
    
    ENV['APP_ENV'] = 'test'
    
    module RSpecMixin
      include Rack::Test::Methods
      def app() Sinatra::Application end
    end
    
    RSpec.configure do |c|
      c.include RSpecMixin
    end
    
    # spec
    
    describe '/' do
      context 'on error' do
        before do 
          allow(SomeModel).to receive(:some_action) { raise ArgumentError }
        end
    
        it 'errors gracefully' do
          get '/'
          expect(last_response.status).to eq 500
        end
      end
    end