how to mock update_all in rspec - ruby-on-rails

I'm coding a request spec and I want to mock update_all in transaction.(My rails version 4.2)
But Not working as I expected
Could you check if my code is correct?
Controller:
def send
ActiveRecord::Base.transaction(joinable: false, requires_new: true) do
begin
return if users.blank?
success_count = users.update_all(xx_created_at: Time.zone.now)
raise StandardError if success_count.zero?
another logic
rescue => e
error = e
raise ActiveRecord::Rollback
ensure
if error.present?
render json: { result: error }, status: :internal_server_error
else
success
end
end
end
end
Spec code:
context '' do
before do
users = instance_double('Dummy users')
allow(users).to receive(:update_all).and_raise(StandardError) # it doesn't work
# allow_any_instance_of(users).to receive(:update_all).with(xx_created_at: Time.zone.now).and_return(0) # it doesn't work too
end
it '500 error' do
post endpoint, payload.to_json, headers
expect(response).to have_http_status :internal_server_error
end
end
Thanks for reading my qeustion.

Related

Mock a singleton class In Ruby on Rails using Rspec [SOLVED]

Hi I Try to create a mock for follow class:
module EstablishmentsQueryService
class << self
def find_by_id(id)
Establishment.find_by!(id:)
rescue ActiveRecord::RecordNotFound
raise EstablishmentNotFoundError.new id
end
end
end
to try test my controller
# frozen_string_literal: true
module Api
module V1
# Controllewr to manager Establishments
class EstablishmentsController < Api::V1::ApiController
before_action :validate_id, only: %i[destroy update show]
before_action :load_establishment, only: %i[destroy update show]
def show; end
def create
#establishment = Establishment.new(establishment_params)
#establishment = EstablishmentService.save(#establishment)
render status: :created
end
def destroy
EstablishmentService.delete(#establishment)
end
def update
#establishment.attributes = establishment_params
#establishment = EstablishmentService.save(#establishment)
end
private
def validate_id
message = I18n.t('establishment_controller.id.invalid', id: params[:id])
UuidValidateService.call(params[:id], message)
end
def load_establishment
#establishment = EstablishmentsQueryService.find_by_id(params[:id])
end
def establishment_params
params.require(:establishment).permit(:name, :cnpj, :description)
end
end
end
end
follow my test:
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe 'Api::V1::Establishments', type: :request do
describe 'GET /api/v1/establishments/:id' do
context 'when has establishment' do
let(:establishment) { build(:establishment, id: p, created_at: DateTime.now, updated_at: DateTime.now) }
before do
allow_any_instance_of(EstablishmentsQueryService).to receive(:find_by_id).and_return(establishment)
get "/api/v1/establishments/#{establishment.id}"
end
it 'then http status is ok' do
expect_status_is_ok
end
it 'has body equal to expected' do
except_field_by_field(establishment, body_to_open_struct, %i[id name cnpj description])
end
end
context 'when has no establishment' do
before do
get "/api/v1/establishments/#{UUID.new.generate}"
end
it 'then http status is not_found' do
expect_status_is_not_found
end
end
context 'when use invalid id' do
before { get "/api/v1/establishments/#{FFaker::Lorem.word}" }
it 'then http status is bad_request' do
expect_status_is_bad_request
end
end
end
describe 'PUT /api/v1/establishments/:id' do
let(:establishments_query_service) { allow(EstablishmentsQueryService) }
let(:establishments_service) { allow(EstablishmentsService) }
context 'when updated with success' do
let(:establishment) { build(:establishment) }
let(:id) { UUID.new.generate }
before do
establishments_query_service.to receive(:find_by_id) { |p| build(:establishment, id: p, created_at: DateTime.now, updated_at: DateTime.now) }
establishments_service.to receive(:save) do |p|
to_return = p
to_return.created_at = DateTime.now
to_return.updated_at = DateTime.now
end
put "/api/v1/establishments/#{id}"
end
it 'then http status is ok' do
expect_status_is_ok
end
it 'has body equal to expected' do
actual = body_to_open_struct
except_field_by_field(establishment, actual, %i[name cnpj description])
expected(actual.id).to eq(id)
end
end
context 'when has no establishment' do
end
context 'when has constraint violation' do
end
end
describe 'DELETE /api/v1/establishments/:id' do
end
describe 'POST /api/v1/establishments' do
end
end
If I work using allow_any_instance_of a test ignore configuration, use a real configuration and fails because has no data stores. If I use double I received a follow error:
Api::V1::Establishments GET /api/v1/establishments/:id when has establishment then http status is ok
Failure/Error: allow_any_instance_of(EstablishmentsQueryService).to receive(:find_by_id).and_return(establishment)
EstablishmentsQueryService does not implement #find_by_id
I think the right away is user allow_any_instance_of because this config is for static methods, but didn't work
how can I mock my class to test my controller? I using Ruby 3.1.2, rails 7.0.3 and rspec-rails 5.1.2
thank you
I found my problem, I forgot to definie expected params in my confi using with()
allow(EstablishmentsQueryService).to receive(:find_by_id).with(establishment.id).and_return(establishment)

How to expect raise ActiveRecord::RecordNotFound rspec?

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
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
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])

rspec - How to test ActiveRecord::RecordNotFound?

I have a method to update people's attribute, and it will rescue ActiveRecord::RecordNotFound if the people cannot be found. The method is:
def update
#people= People.find(params[:id])
if #people.update(people_params)
render json: { success: 'Success' }
else
render :edit
end
rescue ActiveRecord::RecordNotFound => e
render json: { error: 'Failed') }
end
And I want to test the situation when the record not found, here's my test for now:
let(:people) { create(:people) }
let(:people_id) { people.id }
let(:user) { people}
# Other tests...
context 'when person not found' do
let(:exception) { ActiveRecord::RecordNotFound }
# What should I write so that I can let the record not been found?
before { allow(People).to receive(:find).and_raise(exception) }
it 'responds with json containing the error message' do
expect(JSON.parse(response.body)).to eq({error:'Error'})
end
end
I want my test executed under the condition that records not found. But I don't know how to do it. I tried to set let(people) {nil} but it not work. Is there an anyway to do that? Thanks!
This is not a good solution to begin with. In Rails you want to use rescue_from to handle common errors on the controller level.
class ApplicationController
rescue_from ActiveRecord::RecordNotFound, with: :not_found
def not_found
respond_to do |format|
format.json { head :404 }
end
end
end
This lets you use inheritance to DRY your code.
render json: { error: 'Failed') }
Is a huge anti-pattern. If the request failed you should tell the client by sending the correct HTTP status code. Don't reinvent the wheel. Especially not when your solution is a square wheel. If your JS relies on monkeying around with a json response to see if the request was a success or not you're doing it wrong.
If you want to test that your controller handles a missing resource correctly you would do:
let(:people) { create(:people) }
let(:people_id) { people.id }
let(:user) { people}
it "returns the correct response code if the person cannot be found" do
get '/people/notarealid'
expect(response).to have_http_status :not_found
end
This does not use any stubbing and actually tests the implementation.
you can try :
let!(:error_failed) { { error: 'Failed' } }
context 'when people is not found by params' do
it 'return 404 and render json failed'
null_object = double.as_null_object
allow(People).to receive(:find).with(params[:id]).and_raise(ActiveRecord::RecordNotFound.new(null_object)
put :update, format: :json, .....
expect(response.body).to error_dailed.to_json
expect(response.status).to .....
end
end

raise_error spec not returning true in Rspec 3.4

I have the following class, that I am trying to write a spec for:
module IntegrationError
class Error < StandardError; end
class BadRequest < IntegrationError::Error; end
class LogicProblem < IntegrationError::Error; end
def raise_logic_error!(message)
raise IntegrationError::LogicProblem, message
rescue => e
Rails.logger.error e.message
e.backtrace.each do |line|
Rails.logger.error line if line.include?('integrations')
end
end
def raise_bad_request!(message)
raise IntegrationError::BadRequest, message
end
def log_bad_request!(message)
Rails.logger.info message
end
end
with spec
RSpec.describe 'IntegrationError', type: :integration do
let!(:klass) { Class.new { include IntegrationError } }
describe '#log_bad_request!' do
it 'logs it' do
expect(klass.new.log_bad_request!('TESTME')).to be_truthy
end
end
describe '#raise_bad_request!' do
it 'raises it' do
binding.pry
expect(klass.new.raise_bad_request!('TESTME')).to raise_error
end
end
end
the raise_bad_request test returns the error instead of true. Anyone have thoughts on how to write this better to it passes?
I'm using Rails 4 and Rspec 3.4.
If I recall correctly, I believe you need to pass the expectation a block when your raising, like this:
describe '#raise_bad_request!' do
it 'raises it' do
binding.pry
expect{klass.new.raise_bad_request!('TESTME')}.to raise_error
end
end
See docs here
For the raise_error matcher you need to pass a block to expect instead of a value:
expect { klass.raise_bad_request!('TESTME') }.to raise_error
That should do it!

Session is always empty using RSpec

There is the following code:
describe 'Some title' do
before do
session = ActionController::TestSession.new
session[:state] = "12334"
get '/api/v1/menus', format: :json
end
it 'some text' do
expect(response).to be_success
json = JSON.parse(response.body)
puts json
end
end
Code of the controller:
class Api::V1::MenusController < Api::V1::ApiV1Controller
def index
render json: session
end
end
But controller returns an empty session always. How can I fix it?
Try adding this:
describe 'Some title', :type => :controller do
And remove session = ActionController::TestSession.new.
RSpec needs to know you are doing "controller things" in your test. You indicate this as above or by placing the test in spec/controllers.

Resources