ruby-on-railsrubyrspecruby-on-rails-5rspec3

mocking active record invalid exception with Rspec


I'm mocking active record invalid exception in the rspec.

here is the method im facing problem with. image_processing_error checks for the errors of the image object.

def save_image(image)
  begin
    image.save!
      { message: I18n.t("messages.image_saved") , status: 200 }
    rescue ActiveRecord::RecordInvalid
      image_processing_error(image)
  end
end

private

def image_processing_error(image = nil)
  if image && image.errors.any? && image.errors.messages.any?
    { message: image.errors.messages.values[0][0] , status: 422 }
  else
    { message: I18n.t("errors.invalid_request"), status: 422 }
  end
end 

And here is my rspec for the same

# frozen_string_literal: true

require "rails_helper"

RSpec.describe ImagesService do
  describe ".save_image" do
    context "save image throws error" do
      let(:image) { double("image", { "errors": { "messages": { "name": ["is invalid", "must be implemented"]}}}) }

      before do
        allow(image).to receive(:save!).and_raise(ActiveRecord::RecordInvalid)
      end
      subject { described_class.save_image(image) }
      it "raised the error" do
        // I want to test the **ActiveRecord::RecordInvalid
        // I places NoMethodError to just pass the test
        expect { subject }.to raise_error NoMethodError
        expect { subject }.to raise_error ActiveRecord::RecordInvalid
      end
    end
  end
end

I'm getting below error when i read the image error. what is the proper double value i have to keep it to work.

Failures:

  1) ImagesService.save_image save image throws error raised the error
     Failure/Error: expect { subject }.to raise_error ActiveRecord::RecordInvalid
     
       expected ActiveRecord::RecordInvalid, got #<NoMethodError: undefined method `messages' for {:messages=>{:name=>["is invalid", "must be implemented"]}}:Hash> with backtrace:

Solution

  • In this case, the reason why you are not getting an exception on the call to subject is because an exception is not raised by it. Since you have set a rescue block in the save_image, if an exception is thrown inside of it, the rescue block will be called and the exception is not going to be propagated to the caller instead image_processing_error is going to be called.

    So in your spec, when you call subject you are not going to get an exception but instead, you are going to get the parsed errors from image_processing_error which as I understand is actually the expected behavior.

    In this case, what you could do is to test that the result you are getting from subject match with the expected error message that you would get in this case, something like:

    it "returns the error messages" do
        expect(response.body).to include("is invalid", "must be implemented")
    end