I have a method like:
def method
# ..
begin
some_invokation
rescue StandardError
# some_other_stuff
raise
end
# ..
User.create!
end
Right now I can test that this method raises the exception:
expect { method }.to raise_error(StandardError)
But also I would like to test that the user is not created.
expect { method }.not_to change { User.count }
It doesn't work. It shows that the exception was raised. I tried to mock raise invocation:
allow_any_instance_of(described_class).to receive(:raise)
But in this case my method is not interrupted and user is created. Is there any other ways to do it?
Perhaps something like:
expect {
method rescue nil
}.not_to change { User.count }
This might do it:
expect { method }.to raise_error(StandardError)
expect { method rescue 'method rescued' }.to eq('method rescued')
expect { method rescue 'method rescued' }.not_to change { User.count }
Instead of using
expect { robot.greet }.to raise_error
you can method stub the raise to prevent it from breaking the other examples:
class Robot
def greet
puts 'Hello World'
raise 'malfunction'
end
end
RSpec.describe Robot do
let(:robot) { Robot.new }
describe '#greet' do
before do
allow(robot).to receive(:puts)
allow(robot).to receive(:raise)
robot.greet
end
it 'prints a message to console' do
expect(robot).to have_received(:puts).with('Hello World')
end
it 'malfunctions' do
expect(robot).to have_received(:raise)
end
end
end
Related
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.
I have method like this
def className
def method_name
some code
rescue
some code and error message
end
end
So, How to write down the rspec to test rescue block..?
If you want to rescue, it means you expect some code to raise some kind of exception.
You can use RSpec stubs to fake the implementation and force an error. Assuming the execution block contains a method that may raise
def method_name
other_method_that_may_raise
rescue => e
"ERROR: #{e.message}"
end
hook the stub to that method in your specs
it " ... " do
subject.stub(:other_method_that_may_raise) { raise "boom" }
expect { subject.method_name }.to_not raise_error
end
You can also check the rescue handler by testing the result
it " ... " do
subject.stub(:other_method_that_may_raise) { raise "boom" }
expect(subject.method_name).to eq("ERROR: boom")
end
Needless to say, you should raise an error that it's likely to be raised by the real implementation instead of a generic error
{ raise FooError, "boom" }
and rescue only that Error, assuming this is relevant.
As a side note, in Ruby you define a class with:
class ClassName
not
def className
as in your example.
you can stub with return error
for example you have class with method like this :
class Email
def self.send_email
# send email
rescue
'Error sent email'
end
end
so rspec for raising error is
context 'when error occures' do
it 'should return error message' do
allow(Email).to receive(:send_email) { err }
expect(Email.send_email).to eq 'Error sent email brand'
end
end
I'd like to do something like this:
some_method.should_raise <any kind of exception, I don't care>
How should I do this?
some_method.should_raise exception
... doesn't work.
expect { some_method }.to raise_error
RSpec 1 Syntax:
lambda { some_method }.should raise_error
See the documentation (for RSpec 1 syntax) and RSpec 2 documentation for more.
RSpec 2
expect { some_method }.to raise_error
expect { some_method }.to raise_error(SomeError)
expect { some_method }.to raise_error("oops")
expect { some_method }.to raise_error(/oops/)
expect { some_method }.to raise_error(SomeError, "oops")
expect { some_method }.to raise_error(SomeError, /oops/)
expect { some_method }.to raise_error(...){|e| expect(e.data).to eq "oops" }
# Rspec also offers to_not:
expect { some_method }.to_not raise_error
...
Note: raise_error and raise_exception are interchangeable.
RSpec 1
lambda { some_method }.should raise_error
lambda { some_method }.should raise_error(SomeError)
lambda { some_method }.should raise_error(SomeError, "oops")
lambda { some_method }.should raise_error(SomeError, /oops/)
lambda { some_method }.should raise_error(...){|e| e.data.should == "oops" }
# Rspec also offers should_not:
lambda { some_method }.should_not raise_error
...
Note: raise_error is an alias for raise_exception.
Documentation: https://www.relishapp.com/rspec
RSpec 2:
https://www.relishapp.com/rspec/rspec-expectations/v/2-13/docs/built-in-matchers/raise-error-matcher
RSpec 1:
http://apidock.com/rspec/Spec/Matchers/raise_error
http://apidock.com/rspec/Spec/Matchers/raise_exception
Instead of lambda, use expect to:
expect { some_method }.to raise_error
This is applies for more recent versions of rspec, i.e. rspec 2.0 and up.
See the doco for more.
The syntax changed recently and now it is:
expect { ... }.to raise_error(ErrorClass)
From version 3.3 on rspec-expections gem raises a warning for a blank raise_error without a parameter
expect { raise StandardError }.to raise_error # results in warning
expect { raise StandardError }.to raise_error(StandardError) # fine
This gives you a hint that your code may fail with a different error than the test intended to check.
WARNING: Using the raise_error matcher without providing a specific error or message risks false positives, since raise_error will match when Ruby raises a NoMethodError, NameError or ArgumentError, potentially allowing the expectation to pass without even executing the method you are intending to call. Instead consider providing a specific error class or message. This message can be supressed by setting: RSpec::Expectations.configuration.warn_about_potential_false_positives = false.