How to create a ActiveRecord::RecordInvalid for testing?

16,050

Solution 1

EDIT: This is now possible in Rails 5.1.1. The record argument is no longer required after this commit: https://github.com/rails/rails/commit/4ff626cac901b41f86646dab1939d2a95b2d26bd

If you are on a Rails version under 5.1.1, see the original answer below:

It doesn't seem like it is possible to raise an ActiveRecord::RecordInvalid by itself. If you look at the source code for ActiveRecord::RecordInvalid, it requires a record when initializing:

class RecordInvalid < ActiveRecordError
  attr_reader :record # :nodoc:
  def initialize(record) # :nodoc:
    @record = record
    ...
  end
end

(source: https://github.com/rails/rails/blob/master/activerecord/lib/active_record/validations.rb)

Something you could do to work around this would be to simply create an actual record that is invalid and attempt to save it using save! (such as calling User.new.save! when User.name is required). However, keep in mind that this might potentially become a problem in the future if the model you use is changed and it becomes valid inside your test (User.name is no longer required).

Solution 2

None of the above methods worked for me so I finally did the following while doing a spec:

class InvalidRecord
  include ActiveModel::Model
end


raise ActiveRecord::RecordInvalid.new(InvalidRecord.new)

Hope it helps!

Solution 3

I needed to do something similar to test my code's handling of ActiveRecord::RecordInvalid. I wanted to do

allow(mock_ar_object).to receive(:save!).and_raise(ActiveRecord::RecordInvalid)

but that gives ArgumentError: wrong number of arguments (0 for 1) when RSpec tries to instantiate RecordInvalid.

Instead I wrote a RecordInvalid subclass and overrode initialize like this:

class MockRecordInvalid < ActiveRecord::RecordInvalid
  def initialize
  end
end

allow(mock_ar_object).to receive(:save!).and_raise(MockRecordInvalid)

Then a rescue ActiveRecord::RecordInvalid will catch MockRecordInvalid and MockRecordInvalid.new.is_a?(ActiveRecord::RecordInvalid) is true.

Share:
16,050
Eric Francis
Author by

Eric Francis

LinkedIn

Updated on June 04, 2022

Comments

  • Eric Francis
    Eric Francis almost 2 years

    I have this piece of code that I am trying to test:

    def error_from_exception(ex)
      if ex.is_a?(ActiveRecord::RecordInvalid)
    ...
    

    To get into the if block, I need to pass in the correct ex param.

    How do I create an ActiveRecord::RecordInvalid?

    With rspec, I'm trying to do something like this:

    context 'exception is ActiveRecord::RecordInvalid' do
      it 'returns the validation error' do
        begin
          raise ActiveRecord::RecordInvalid
        rescue Exception => ex
          binding.pry
          ###
          # From my pry session:
          # $ ex
          # $ <ArgumentError: wrong number of arguments (0 for 1)>
          # $ ex.class
          # $ ArgumentError < StandardError
          ###
        end
    
    
      end
    end
    

    How do I find out what argument type the library is looking for?

    RecordInvalid link

  • Eric Francis
    Eric Francis over 9 years
    Yep this is exactly what I ended up doing. It works with a valid record too, there just won't be any messages. let!(:user) { FactoryGirl.create(:user) } let!(:ex) { ActiveRecord::RecordInvalid.new(user) }
  • Gander
    Gander over 3 years
    Please edit your answer to improve your code block (do formatting applied) and include an explanation of how this works and why it is of solution to the problem described in the question. See How to Answer.
  • alexventuraio
    alexventuraio over 2 years
    It worked for me, and I just added a new message method so that I can do something like error.message.include?("Kind") inside the rescue section.
  • alexventuraio
    alexventuraio over 2 years
    It was useful when I just do not need any specific error message but just the ActiveRecord::RecordInvalid object.