Home > database >  How to expect raise ActiveRecord::RecordNotFound rspec?
How to expect raise ActiveRecord::RecordNotFound rspec?

Time:12-06

How to get the test pass for this error?

Rspec controller and result

context 'invalid confirmation_token' do
      subject do
        post signup_step5_path,
             params: {
               user: {
                 password: 'hoge',
                 password_confirmation: 'hoge',
                 confirmation_token: 'wrong_token'
               }
             }
      end

      let(:user) { User.find_by(confirmation_token: 'testtesttest') }

      it 'does not update user attributes and never create an end_point record' do
        expect { subject }.raise_error(ActiveRecord::RecordNotFound)

expected ActiveRecord::RecordNotFound but nothing was raised

controller-method I rescue ActiveRecord::RecordNotFound and render 404 page in the private method.


class Users::SignupController < ApplicationController
  layout 'devise'

  rescue_from ActiveRecord::RecordNotFound, with: :render404


def step5
    @user = User.find_by(confirmation_token: step5_params[:confirmation_token])
    raise ActiveRecord::RecordNotFound unless @user
    .....
    end

private

def render404(error = nil)
    logger.info "Rendering 404 with exception: #{error.message}" if error
    render file: Rails.root.join('public/404.ja.html'), status: :not_found
  end

end

CodePudding user response:

Assuming it's a request spec, the request will return HTTP 404, and you can set an expectation for that:

is_expected.to be_not_found

Side note:

@user = User.find_by(confirmation_token: step5_params[:confirmation_token])
raise ActiveRecord::RecordNotFound unless @user

can be simplified to just:

@user = User.find_by!(confirmation_token: step5_params[:confirmation_token])

CodePudding user response:

First its probably a good idea to explain that the exception matchers will only actually match uncaught exceptions. Thats because its basically just a rescue statement and rescues the exception as it bubbles up the call stack and its intended to test that a peice of code raises an exception which its up to the consumer to catch - that is an example of testing the behavior.

Testing that code raises and rescues a exception on the other hand is testing how it does its job.

def foo
  raise SomeKindOfError
end

def bar
  begin 
    raise SomeKindOfError
  rescue SomeKindOfError
    puts "RSpec will never catch me!"
  end
end

describe "#foo" do
  it "raises an exception" do
    expect { foo }.to raise_exception(SomeKindOfError)
  end
end

describe "#bar" do
  it "rescues the exception" do
    expect { bar }.to_not raise_exception(SomeKindOfError)
  end
end

When you use rescue_from its basically just syntactic sugar for using an around_action callback to rescue the given exception:

class ApplicationController
  around_action :handle_errors

  private

  def handle_errors
    begin 
      yield
    rescue SomeKindOfError
      do_something
    end
  end
end

While RSpec did at one point have bypass_rescue for controller specs the use of controller specs is greatly discouraged by both the Rails and RSpec teams and you're really just testing the implementation instead of the behavior.

Instead you should test what the actual controller does instead of how it does it.

context 'invalid confirmation_token' do
  # explicit use of subject is a code smell
  before do
    post signup_step5_path,
      params: {
        user: {
          password: 'hoge',
          password_confirmation: 'hoge',
          confirmation_token: 'wrong_token'
        }
      }
  end
  let(:user) { User.find_by(confirmation_token: 'testtesttest') }
  
  it 'does not update the users password' do
    expect(user.valid_password?('hoge')).to be_falsy
  end
  
  it 'returns a 404 - NOT FOUND' do
    expect(response).to have_http_status(:not_found)
  end
  
  # using Capybara in a feature spec is a better way to do this.
  it 'renders something' do
    expect(response.body).to match("Oh Noes!") 
  end
end
  • Related