Continue after exception in RSpec - ruby-on-rails

Is there any way to have RSpec continue processing specifications after an exception is raised?
This is what my spec task looks like:
SPEC_PATTERN = "spec/**/*_spec.rb"
Spec::Rake::SpecTask.new() do |t|
t.spec_files = FileList[SPEC_PATTERN]
t.verbose = true
t.spec_opts = ["--format", "html:spec/spec_report.html"]
t.fail_on_error = false
t.rcov = true
t.rcov_dir = 'coverage'
t.rcov_opts = ['--exclude', 'spec']
end

what about using "should raise_exception"?
http://rspec.rubyforge.org/rspec/1.3.0/classes/Spec/Matchers.html#M000183

rspec does capture exceptions and report them as failures, in much the same way test/unit does. If you're seeing the task exiting it's because the exception is either outside of the code that rspec is handling, or it might be a syntax error.
HTH,
David

it "should not raise an exception" do
expect {
raise Exception unless true
}.should_not raise_exception
end

Related

Expected Exception but nothing was raised Rails Mailer (the raising-exception method seems not to be called)

I'm having RSpec which expect some exception to be raised. However, when invoking the method, I got error: expected Exception but nothing was raised
my code:
require 'rails_helper'
RSpec.describe ReportMailer, type: :mailer do
let(:csv_file) { "data\n1\n2" }
let(:filename) { 'dummy.csv' }
let(:options) do
{
filename: filename,
to: user.email,
subject: "Transaction Report - #{filename}"
}
end
it 'raise error' do
expect do
ReportMailer.notify(options, nil)
end.to raise_error(SomeExceptions::InvalidFile)
end
end
The problem is that if I just use normal expect call, let's say
expect(described_class.notify(dummy_options, nil)).to eq 1
RSpec show failure/error that I expect before:
Failures:
1) ReportMailer raise error
Failure/Error: raise SomeExceptions::InvalidFile
SomeExceptions::InvalidFile:
The file is invalid
# ./app/mailers/report_mailer.rb:5:in `notify'
# ./spec/mailers/report_mailer_spec.rb:37:in `block (2 levels) in <top (required)>'
My notify method is below:
require 'some_cms_exceptions'
class ReportMailer < ApplicationMailer
def notify(options, csv)
binding.pry
raise SomeExceptions::InvalidFile
validate(options)
attachments[options[:filename]] = { mime_type: 'text/csv', content: csv }
mail(to: options[:to], subject: options[:subject])
end
private
def validate(options)
raise SomeExceptions::InvalidMailOptions unless !options[:to].blank? && !options[:filename].blank?
end
end
I then put binding.pry in notify method and found out that:
if we use expect block, i.e. expect.{...}.to, notify method is not executed.
But if we use normal expect, i.e. expect(...).to ,notify method is executed.
May I know why it behaves like this ? Because other SO questions showed it works by using expect block.
Thank you
At line 5 you are raising SomeExceptions::InvalidFile exception while you are expecting different error in the expectation block
raise_error(SomeExceptions::InvalidMailOptions)
either you replace the exception you are expected or catch all exception using just raise_error without passing any error class(not recommended but for sake of testing).
The answer is as commented by #amit-patel, we need to add deliver_now to really execute mailer RSpec test case.
it 'should send email ' do
expect { ReportMailer.notify(options, nil).deliver_now }.to raise_error(SomeExceptions::InvalidMailOptions)
end

How to catch raised exception in rake task

I have a rake task that loops through rows in CSV file, and inside that loop, there's a begin/rescue block to catch any possible raised exception. But when I run it, it keeps on saying 'rake aborted!' and it is not entering the rescue block
CSV.foreach(path, :headers => true) do |row|
id = row.to_hash['id'].to_i
if id.present?
begin
# call to mymethod
rescue => ex
puts "#{ex} error executing task"
end
end
end
...
def mymethod(...)
...
begin
response = RestClient.post(...)
rescue => ex
raise Exception.new('...')
end
end
Expected: It should finish looping all the rows of the CSV
Actual result: It stops after reaching the 'raise' exception saying that:
rake aborted!
Exception: error message here
...
Caused by:
RestClient::InternalServerError: 500 Internal Server Error
You can use next to skip the faulty step of loop:
CSV.foreach(path, :headers => true) do |row|
id = row.to_hash['id'].to_i
if id.present?
begin
method_which_doing_the_staff
rescue SomethingException
next
end
end
end
And raise the exception inside your method:
def method_which_doing_the_staff
stuff
...
raise SomethingException.new('hasd')
end
I solved this issue by just commenting out the line that is raising an exception because it seems like it the quickest fix for now.
# raise Exception.new('...')
I'm still open to other suggestions if there are any better ways to do it.

ActionDispatch::IntegrationTest suppresses exceptions

When debugging failing integration tests, I keep running into the same problem where the exceptions raised in my code are suppressed and not shown in the testing output.
For example, for the following controller and test:
class RegistrationController::ApplicationController
def create
# some code that raises an exception
end
end
class RegistrationFlowTest < ActionDispatch::IntegrationTest
test 'user registers successfully' do
post sign_up_path, params: { username: 'arnold', password: '123' }
assert_response :success
end
end
The output is something like
Minitest::Assertion: Expected response to be a <2XX: success>, but was a <500: Internal Server Error>
Is there a way to see the exact raised exception? Instead of just the difference of HTTP response code?
Thanks!
Simon
My recommended approach to this issue would be to actually parse the response provided by Rails (at least by default in test and development environments) which includes the stacktrace for the error and handle that in the case that your test fails. This has the advantage that it won't output the stacktrace when errors are raised that don't result in failing tests (e.g. scenarios where you are intentionally testing how failures are handled).
This little module that I've crafted will allow you to call assert_response_with_errors to assert the response to a call but output the exception message and stack trace in a readable format when the response is not what you expected.
module ActionDispatch
module Assertions
module CustomResponseAssertions
# Use this method when you want to assert a response body but also print the exception
# and full stack trace to the test console.
# Useful when you are getting errors in integration tests but don't know what they are.
#
# Example:
# user_session.post create_gene_path, params: {...}
# user_session.assert_response_with_errors :created
def assert_response_with_errors(type, message = nil)
assert_response(type, message)
rescue Minitest::Assertion => e
message = e.message
message += "\nException message: #{#response.parsed_body[:exception]}"
stack_trace = #response.parsed_body[:traces][:'Application Trace'].map { |line| line[:trace] }.join "\n"
message += "\nException stack trace start"
message += "\n#{stack_trace}"
message += "\nException stack trace end"
raise Minitest::Assertion, message
end
end
end
end
To use this, you need to include it into ActionDispatch::Assertions before Rails has loaded its stack in your test_helper.rb. So just prepend the include into your test_helper.rb, like this:
ActionDispatch::Assertions.include ActionDispatch::Assertions::CustomResponseAssertions
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
...
This will happen because Rails controllers by default handle exceptions and raise the 500 status, making the exceptions invisible to the test suite (which is very helpful if errors are raised in unit tests of a model). Options for disabling this in your test suite, or alternative workarounds, are discussed here.
The key lines of code from that link, which should be added to test/integration/integration_test_helper.rb:
ActionController::Base.class_eval do
def perform_action
perform_action_without_rescue
end
end
Dispatcher.class_eval do
def self.failsafe_response(output, status, exception = nil)
raise exception
end
end
EDIT: I've noticed that that link is quite old now. I'm really familiar with Rack, so whilst the first block looks ok to me, I'm not sure if the second will still be current. You might need to have a look at the relevant current Rails guide if it needs bringing up to date.

Rspec - How to create macros with tags?

Is there a way to run Rspec macros conditionally ?
For example ability to filter macros using
RSpec.configure do |c|
c.filter_run_excluding :broken => true
end
## This should get skipped
it_should_validate_with_macro :some_param, :broken => true
Note: This is to invoke group of tests dynamically. So pending kinda solution is not I'm looking for.
describe "an example" do
it "is implemented but waiting" do
pending("something else getting finished")
this_should_not_get_executed
end
end
from https://relishapp.com/rspec/rspec-core/v/2-0/docs/pending/pending-examples
You can run specs in the same process, and you can also do things like capture the output.
But a simple example that might work for you is simply to create a ruby script:
require 'rspec/core'
RSpec.configuration.add_setting(:some_setting)
RSpec.configuration.some_setting = false
RSpec::Core::Runner.run(['spec/models/bar.rb'])
RSpec.clear_examples
if RSpec.configuration.some_setting
RSpec::Core::Runner.run(['spec/models/foo.rb'])
end
Then, in your rspec script that will modify the setting:
RSpec.describe 'bar' do
it 'bar' do
RSpec.configuration.some_setting = true
end
end
This will conditionally run the spec in foo.rb.

Rails rake task invoke error catching

I have a rails code snippet that does a rake file from a model.
Rake.application.rake_require '../../lib/tasks/master_load'
fork do
results = capture_stdout {Rake.application['db:seed:excel:to_yaml'].invoke}
end
capture_stdout is for printing out the logs
def self.capture_stdout
s = StringIO.new
oldstdout = $stdout
$stdout = s
yield
s.string
Rails.logger.info "#{s.string}"
ensure
$stdout = oldstdout
end
it works, but when the rake encounters error and fails.
it fails silently.
is there a way to know if error has occured and perhaps get the error log?
Use rescue to catch and handle error
def self.capture_stdout
s = StringIO.new
oldstdout = $stdout
$stdout = s
yield
s.string
Rails.logger.info "#{s.string}"
rescue Exception => e
# handle e - exception object
ensure
$stdout = oldstdout
end
Ensure in this case silently makes action after exception handling

Resources