Im trying to write some tests to check before validation of a model in rspec
class MyClass < ApplicationRecord
before_validation :generate_anonymous_id
def generate_anonymous_id
retries ||= 1
self.uuid = SecureRandom.uuid
self.aid = Digest::SHA256.hexdigest(uuid)[0...15]
raise ActiveRecord::RecordNotUnique if OtherClass.find_by_sfid(aid) ||
MyClass.find_by_aid(aid)
rescue ActiveRecord::RecordNotUnique => error
Rails.logger.warn("Encountered duplicate uuid/aid")
retries += 1
retry if retries <= 3
Rails.logger.warn("Raising exception after 3 retries")
raise error
end
end
here is my rspec
# frozen_string_literal: true
require "rails_helper"
RSpec.describe MyClass, type: :model do
describe "model validation" do
let(:my_class) do
MyClass.create
end
context "valid" do
it "allows to create a user aid" do
expect(MyClass.new.valid?).to be_truthy
end
end
context "duplicate" do
subject do
MyClass.new
end
it "allows to create a user aid" do
subject.uuid = my_class.aid
expect(subject.valid?).to be_falsey
end
end
end
end
Im trying to test the rescue block and my test always passes. I'm not able to override my subject and i dont know what is the mistake im doing.
Thanks in advance
Your before_validation overwrites the subject.uuid, which is happening when you call subject.valid? i.e. generate_anonymous_id forces it to be valid
I stubbed the code which raises the exception and it worked.
context "invalid record" do
context "capture exception" do
let!(:user) { create(:custom_user) }
before { allow(UserUuid).to receive(:find_by_sfid).and_return(true)}
subject { described_class.new}
it "raises exception" do
expect { subject.valid? }.to raise_error(ActiveRecord::RecordNotUnique)
end
end
end
Related
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])
I have same problem. I don't know how to test an invalid record in this case. Help me please someone.
Need to look on: subject with wrong sms_campaign_id and in "it"
error.log
ActiveRecord::RecordNotFound: Couldn't find SmsCampaign with 'id'=12314151 [WHERE sms_campaigns.company_id = 66 AND sms_campaigns.company_id = ?]
messages_controller_spec.rb
describe PrivateApi::Company::SmsCampaigns::MessagesController do
let(:company) { create :company, :completed, :with_superuser }
let(:sms_campaign) { create :sms_campaign, company: company }
describe 'GET index' do
let(:user) { create(:user, company: company) }
before(:each) { signin user }
context 'when user logged with invalid sms_campaign_id' do
subject(:index_action_invalid) { get :index, sms_campaign_id: 12314151 }
it 'The wrong sms_campaingn_id' do
index_action_invalid
expect(response).to have_http_status(200)
expect(response.content_type).to eq(Mime::JSON)
end
end
end
end
messages_controller.rb
# frozen_string_literal: true
module PrivateApi
module Company
module SmsCampaigns
# Resource controller to fetch all the additional messages of given SMS campaign
class MessagesController < ::PrivateApi::Company::BaseController
def index
sms_campaign = SmsCampaign.where(company: #company).
accessible_by(current_ability, :read).find(params[:sms_campaign_id])
messages = sms_campaign.messages.order(send_at: :desc)
render json: messages
end
end
end
end
end
Assuming you are not rescuing ActiveRecord::RecordNotFound from somewhere in your controller(s), then this code will raise the error, and return a status code 404.
If you are trying to test that the exception will be raised, you need to use the matcher expect { <block_of_code> }.to raise_error <error_class>
If an exception should not be raised, then check your exception handler, because it is not being invoked.
If you want to do this without raising an ActiveRecord::RecordNotFound, you will need to switch find(id) with where(id: id).first
I have Sidekiq worker.
class DeliverSmsMessageWorker
include Sidekiq::Worker
def perform(sms_message_id)
....
rescue StandardError => e
Rails.logger.error("SmsMessageWorker ERROR: #{e}")
Bugsnag.notify(e)
end
end
And i write spec, but i get error when i try test Rails.looger.
describe DeliverSmsMessageWorker, type: :worker do
subject(:worker) { DeliverSmsMessageWorker }
context 'on exceptions' do
let(:error) { StandardError.new('test exception') }
before do
allow(worker).to receive(:perform_async).with(sms_message.id).and_raise(error)
end
it 'message in logger' do
Sidekiq::Testing.inline! do
worker.perform_async(sms_message.id)
expect(Rails.logger).to receive(:error).and_call_original
end
end
end
end
After when i run this specs, I get the error. but why?
Is there any point in testing these two lines?
1) DeliverSmsMessageWorker on exceptions message in the logger
Failure/Error: worker.perform_async(sms_message.id)
StandardError:
test exception
Maybe you should use the block syntax for raise_error here:
Sidekiq::Testing.inline! do
expect { worker.perform_async(sms_message.id) }.to raise_error { |error|
expect(Rails.logger).to receive(:error).and_call_original
}
end
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!
Help me make this test pass:
Here is an example of some rspec code,
class User
attr_accessor :count
def initialize
#count = 0
end
# sometimes raises
def danger
puts "IO can be dangerous..."
rescue IOError => e
#count += 1
end
#always raises
def danger!
raise IOError.new
rescue IOError => e
#count += 1
end
end
describe User do
describe "#danger!" do
it "its rescue block always increases the counter by one" do
allow(subject).to receive(:'danger!')
expect {
subject.danger!
}.to change(subject, :count).by(1)
end
end
describe "#danger" do
context "when it rescues an exception" do
it "should increase the counter" do
allow(subject).to receive(:danger).and_raise(IOError)
expect {
subject.danger
}.to change(subject, :count).by(1)
end
end
end
end
I've also created a fiddle with these tests in it, so you can just make them pass. Please help me test the rescue block of a method!
Background:
My original question went something like this:
I have a method, like the following:
def publish!(resource)
published_resource = resource.publish!(current_project)
resource.update(published: true)
if resource.has_comments?
content = render_to_string partial: "#{ resource.class.name.tableize }/comment", locals: { comment: resource.comment_content_attributes }
resource.publish_comments!(current_project, published_resource.id, content)
end
true
rescue Bcx::ResponseError => e
resource.errors.add(:base, e.errors)
raise e
end
And I want to test that resource.errors.add(:base, e.errors) is, in fact, adding an error to the resource. More generally, I want to test the rescue block in a method.
So I'd like to write code like,
it "collects errors" do
expect{
subject.publish!(training_event.basecamp_calendar_event)
}.to change(training_event.errors.messages, :count).by(1)
end
Of course, this raises an error because I am re-raising in the rescue block.
I've seen a few answers that use the old something.stub(:method_name).and_raise(SomeException), but rspec complains that this syntax is deprecated. I would like to use Rspec Mocks 3.3 and the allow syntax, but I'm having a hard time.
allow(something).to receive(:method_name).and_raise(SomeException)
would be the new allow syntax. Check out the docs for reference.
I was misunderstanding what the allow syntax is actually for. So to make my example specs pass, I needed to do this:
describe "#danger" do
context "when it rescues an exception" do
it "should increase the counter" do
allow($stdout).to receive(:puts).and_raise(IOError) # <----- here
expect {
subject.danger
}.to change(subject, :count).by(1)
end
end
end
This thing that I'm stubing is not the method, or the subject, but the object that might raise. In this case I stub $stdout so that puts will raise.
Here is another fiddle in which the specs are passing.