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

Viewed 1403

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?

2 Answers

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

Use the halt method:

require 'sinatra'

get '/' do
  halt 500, {
    success: 'false',
    response: 'Internal server error'
  }.to_json
end
Related