Testing custom exception with a single expect? - ruby

I have a function that I want to test raises an exception on an input, but that exception also carries some more information than just a plain message, and I want to test that too. So I did something like this as seen in the rspec documentation:
it 'raises the correct exception' do
expect { my_call }.to raise_error do |error|
expect(error.some_field).to eq('some data')
end
end
This works great, however it runs afoul of the RSpec/MultipleExpectations cop:
RSpec/MultipleExpectations: Example has too many expectations [2/1]
From what I can tell it is impossible to use raise_error in block form like this without more than one expect, so what gives? Is there some way to somehow save the raised exception outside the example so I can spec it normally, without doing something horrible involving rescue in the specs? Or should I use a custom raise_custom_error matcher?

Rubocop by default I think enables the warning that you see which says to only have one expect in each it block. You can disable this in rubocop.yml by adding this:
# Disables "Too many expectations."
RSpec/MultipleExpectations:
Enabled: false
Or if you only want to disable it for your specific spec you can do so by adding comments like this, note you can disable any rubocop rule this way by using the rule name in comments:
# rubocop:disable RSpec/MultipleExpectations
it 'raises the correct exception' do
expect { my_call }.to raise_error do |error|
expect(error.some_field).to eq('some data')
end
end
# rubocop:enable RSpec/MultipleExpectations
it 'does something else' do
expect(true).to be true
end
For more rubocop syntax options see this answer

Related

Best Practices for RSpec expect raise_error

I have some code for RSpec.
My (or not my) Class can (or not) call specific exception.
Ex, Class::Foo::Bar::TimeOutError.
I want check it.
describe :describe_description do
before :all do
object = Class.new
# method can be call Class::Foo::Bar::TimeOutError
object.method
end
it :exception do
expect {}.to_not raise_error Class::Foo::Bar::TimeOutError
end
end
This code worked, but RSpec write a Warning:
WARNING: Using expect { }.not_to raise_error(SpecificErrorClass) risks false positives, since literally any other error would cause the expectation to pass, including those raised by Ruby (e.g. NoMethodError, NameError and ArgumentError), meaning the code you are intending to test may not even get reached. Instead consider using expect {}.not_to raise_error or expect { }.to raise_error(DifferentSpecificErrorClass). This message can be suppressed by setting: RSpec::Expectations.configuration.on_potential_false_positives = :nothing.
How can I fix my code so that the RSpec works correctly and does not issue warnings?
No, there isn't a way to apply expect {}.to_not raise_error in the spec without this warning (that's the whole point of adding this warning).
It doesn't make sense logically because you don't need to explicitly check a type of error is not raised.
Scenario 1. The code executes successfully (i.e no errors at all). Then a passing test itself implies there's no Class::Foo::Bar::TimeOutError thrown.
Scenario 2. Another type of error is thrown. Then expect {}.to_not raise_error Class::Foo::Bar::TimeOutError isn't testing anything, it still just passes which creates false positive.
Scenario 3. If you really want to use it, suppress with the setting on_potential_false_positives then the test will continue without the warning. If it passes, then this has no difference with the scenario which makes expect {}.to_not raise_error redundant anyway.
For a future-proof solution I recommend setting
RSpec::Expectations.configuration.on_potential_false_positives = :raise
And fixing any offending examples from
expect { some_call }.to not_raise_error(SomeErrorClass)
to
expect { some_call }.to not_raise_error
# OR
expect { some_call }.to raise_error(SomeOtherErrorClass)

What is this notation in rspec: it { is_expected.to ... }

I am reading the 'Better specs' page, and in one of the examples it says:
context 'when logged in' do
it { is_expected.to respond_with 200 }
end
context 'when logged out' do
it { is_expected.to respond_with 401 }
end
And I don't recognize this. I usually would do:
context 'when logged out' do
it 'responds with a 401' do
expect(response).to eq(401)
end
end
What is that syntax?
This is something introduced heavily in Rspec 3.XX. It's under the one line syntax guides as outlined here
RSpec supports a one-liner syntax for setting an expectation on the
subject. RSpec will give the examples a doc string that is auto-
generated from the matcher used in the example. This is designed
specifically to help avoid duplication in situations where the doc
string and the matcher used in the example mirror each other exactly.
When used excessively, it can produce documentation output that does
not read well or contribute to understanding the object you are
describing.
This comes in two flavors:
is_expected is defined simply as expect(subject) and is designed for
when you are using rspec-expectations with its newer expect-based
syntax.
it { is_expected.to respond_with 200 }
that is more readable.
Why you added description if you can read it from test.
Your code should be simple, smart and readable in the same time...
but if you realy want, you can add even novel... up to you :)

Padrino rspec controller testing always green

I am testing my controller with rspec on padrino with this code:
https://gist.github.com/anonymous/8d0df4c189e99c7cb7ea
If I run the test everythings goes fine and all the test will be green.
The problem is that those test must fail! The sign_in_admin on the before block doesn't allow the user to login and make the post call and also if I change the line
last_response.should_not be_ok
with
last_response.should be_ok
the test is always green.....
I don't know where I am wrong.
Here is my spec_helper.rb
https://gist.github.com/anonymous/6442d02654cbee2cf3b5
The reason your code is always passing is because your test is of the form:
lambda {}.should {}
which is equivalent to
lambda {}.should
since should ignores any block passed to it. That is further equivalent to:
lambda {}.should be_truthy
which always succeeds because lambda {} is a Proc, which is truthy.
You should only send should to a Proc if you want should to execute the proc for purposes of evaluating side effects (e.g. raising errors, interacting with other objects), for which there are matchers of the form raise_error and change. The same is true for passing a block to expect, which is the current syntax.
In your case, you can simply execute the code that in your current lambda expression and then check the value of last_response, as in:
it '...' do
post ...
last_response.should be_ok
end
or the more current:
it '...' do
post ...
expect(last_response).to be_ok
end

How to stub a ruby method and conditionally raise an exception based upon state of object

In short, I want to raise an exception via a stubbed method, but only if the object that has the stubbed method has a particular state.
Mail::Message.any_instance.stub(:deliver) do
if to == "notarealemailaddress!##!##"
raise Exception, "SMTP Error"
else
return true
end
end
This doesn't work, because the context inside the stub block is: RSpec::Core::ExampleGroup::Nested_1::Nested_2::Nested_2.
How do I get access to the stubbed object?
using ruby 2, rspec 2.
The actual scenario is I have an app that sounds out thousands of emails in batches and I have code that catches SMTP exceptions, logs the batch, and proceeds. So I want to test sending several batches, where one of the batches in the middle throws an exception.
It looks like this is solved in the latest(currently alpha) version of Rspec v3:
https://github.com/rspec/rspec-mocks/commit/ebd1cdae3eed620bd9d9ab08282581ebc2248535#diff-060466b2a68739ac2a2798a9b2e78643
it "passes the instance as the first arg of the implementation block" do
instance = klass.new
expect { |b|
klass.any_instance.should_receive(:bees).with(:sup, &b)
instance.bees(:sup)
}.to yield_with_args(instance, :sup)
end
I believe you specify the arguments using the with method, so in your case it would be something along the lines of:
Mail::Message.any_instance.stub(:deliver).with(to: "notarealemailaddress!##!##") do
raise Exception, "SMTP Error"
end
There's full documentation here:
https://www.relishapp.com/rspec/rspec-mocks/v/2-3/docs/method-stubs
Ok, here's how you can get this behavior fairly easily without upgrading:
class Rspec::Mocks::MessageExpectation
# pulling in behavior from rspec v3 that I really really really need, ok?
# when upgrading to v3, delete me!
def invoke_with_orig_object(parent_stub, *args, &block)
raise "Delete me. I was just stubbed to pull in behavior from RSpec v3 before it was production ready to fix a bug! But now I see you are using Rspec v3. See this commit: https://github.com/rspec/rspec-mocks/commit/ebd1cdae3eed620bd9d9ab08282581ebc2248535#diff-060466b2a68739ac2a2798a9b2e78643" if RSpec::Version::STRING > "2.99.0.pre"
args.unshift(#method_double.object)
invoke_without_orig_object(parent_stub, *args, &block)
end
alias_method_chain :invoke, :orig_object
end
Drop that at the bottom of your spec file. You'll notice I even add a check to raise an error once RSpec is upgraded. boom!

How do I parameterise RSpec tests so I can test the same behaviour under slightly different conditions

I'm implementing a service that has several different ways it can be accessed:
Using simple query parameters
With parameters encoded as a Javascript object
For some calls both GET and POST are supported, with POST being used when there is large amounts of data being sent to the service.
What's the best way to structure my RSpec tests to avoid unnecessarily repeating code, allowing me to run the same basic assertions each time?
I'm already using shared_examples to capture some comment tests for things like response code, mimetype, etc. But I'm wondering whether there are other options, particularly when I want to invoke the service using all request methods AND a range of expected inputs and outputs.
The way I would do it in this case is to specify the request as a lambda that performs it. That way I can refer to it in my shared specs and set a different one for each type of request.
I like using rspec describe blocks when its sets an expectation, in this case that a particular request method is used. The whole thing will look something like this:
describe FooController do
shared_examples_for "any request" do
it "assigns foo" do
#request.call
assigns[:foo].should == "bar"
end
it "does not change the number of bars" do
#request.should_not change(Bar, :count)
end
end
context "using GET" do
before do
#request = lambda { get "index" }
end
it_should_behave_like "any request"
end
end
An even cleaner way is to use the 'let' construct, although it may be a step too deep in rSpec magic for a novice:
describe FooController do
shared_examples_for "any request" do
it "assigns foo" do
request.call
assigns[:foo].should == "bar"
end
it "does not change the number of bars" do
request.should_not change(Bar, :count)
end
end
context "using GET" do
let(:request) { lambda { get "index" } }
it_should_behave_like "any request"
end
end

Resources