Test that the 'save' function fails when invalid data is passed. (Ruby Rspec)

Viewed 414

I am working on a project that has the following validation criteria in the "project" model:

validates :project_name,
            presence: true,
            format: { with: /\A[a-zA-Z\s_-]+\z/,
                      message: 'allows letters, spaces, underscores, and hyphens' }
  validates :jira_id,
            uniqueness: true,
            presence: true,
            format: { with: /\A[A-Z]+-[0-9]+\z/,
                      message: 'allows capitalized letters followed by a hyphen and numbers' }
  validates :capacity,
            presence: true,
            numericality: { only_integer: true, greater_than_or_equal_to: 0 }
  validates :openings,
            presence: true,
            numericality: { only_integer: true, greater_than_or_equal_to: 0 }

  validates_format_of :last_status_change_dt,
                      with: /\d{4}-\d{2}-\d{2}/,
                      message: 'must be formatted as YYYY-MM-DD',
                      on: :save

  validates_presence_of :last_status_change_dt

The projects_controller:

def create
    ActiveRecord::Base.transaction do
      parameters = project_params.dup
      parameters[:language_id] = get_language_id(params[:language_name]) if parameters[:language_id].nil?
      @project = Project.new(parameters)
      if @project.save
        render json: @project, status: :created, location: @project
      else
        render json: @project.errors, status: :not_acceptable
        raise ActiveRecord::Rollback
      end
    end
  rescue ActiveRecord::ActiveRecordError
    render json: { text: 'Project was not created' }, status: :internal_server_error
  end

In the spec file, FactoryBot build and create functions are used.

In the tests right now, the validation is dependent on the project model, but I want to test the save function. How can I mock the save function to fail?

1 Answers

You can use allow_any_instance_of, but that's a bit of a hammer. Instead, make a double and mock Project.new to return it.

context 'Project#save raises ActiveRecord::ActiveRecordError' do
  let(:project) {
    # Set up the double to fail on save.
    instance_double("Project").tap { |project|
      allow(project).to receive(:save).and_raise(ActiveRecord::ActiveRecordError)
    }
  }

  before {
    # Set up Project.new to return the double
    allow(Project).to receive(:new).and_return(project)
  }
end

Note that it's quite unusual for save to raise ActiveRecord::ActiveRecordError, if it does something very unusual has gone wrong. You generally don't rescue it in a controller.

Related