RSpec is ignoring my rescue block in controller

Viewed 1085

I have a Rails controller which does a health check of the database, like this:

def health_check
  begin
    status = ActiveRecord::Base.connected? ? 'UP' : 'DOWN'
  rescue
    status = 'DOWN'
  end
  render text: status
end

I'm trying to create a RSpec controller spec for this, and the specs for positive and negative responses work, but when I try to test the rescue block, RSpec seems to be ignoring it:

RSpec.describe(HealthCheckController) do
  context 'When the check raises an exception' do
    before :each do
      allow(ActiveRecord::Base).to receive(:connected?).and_raise(OCIException) # Using Oracle
    end

    it 'should render text DOWN' do
      # First attempt
      get :health_check
      expect(response.body).to eq 'DOWN'

      # Second attempt
      expect { get :health_check }.to raise_error
      expect(response.body).to eq 'DOWN'
    end
  end
end

I tried the spec with both of the code inside the it block above (separatedly).

For the first, RSpec failed with this:

 Failure/Error: get :health_check
     OCIException:
       OCIException

For the second, it also failed, with this more "familiar" message instead:

Failure/Error: expect(response.body).to eq 'DOWN'

       expected: "DOWN"
            got: ""

       (compared using ==)

I also checked the HTTP code being returned by response, and it's 200, so the response itself is fine, no 500 error.

It's as if RSpec is simply bypassing the rescue block and not running it. What may be causing this? I'm not using the bypass_rescue RSpec method anywhere, this is also a new project.

Using:

Rails 4.2.6
Rake 10.5.0
RSpec-core 3.3.2
RSpec-rails 3.3.3
1 Answers

Actually, the problem has nothing to do with the rescue block in your controller. Rather, it is caused by the fact that you have overriden ActiveRecord::Base#connected? by stubbing it in your before block.

Calling render in the controller initiates a connection to the database. Somewhere in the process ActiveRecord::Base#connected? gets called (twice, actually), but instead of returning what it is supposed to return, it raises an exception that you have defined in your setup.

In your first example, the exception is raised before your expectation, thus the explicit exception name in the failure message.

In your second example, the exception is suppressed by your raise_error expectation, so RSpec is able to proceed to the next one. Where it fails because the render call in the controller never runs (due to the error), and as a result, the response body never gets a chance to be populated.

As a confirmation that ActiveRecord::Base#connected? does get called when you call render, try running the following spec. You will see that it's green, meaning that the said method has been called twice in the process. You can also try replacing render with head :ok and will see that in this case ActiveRecord::Base#connected? gets called only once.

RSpec.describe ApplicationController do
  controller do
    def index
      render json: ''
    end
  end

  specify 'test' do
    expect(ActiveRecord::Base).to receive(:connected?).twice
    get :index
  end
end
Related