Best Practices for RSpec expect raise_error - ruby

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)

Related

RSpec: Expecting a method to be called causes that method to not actually be called

I have some code that could be represented in very simple terms as:
def method_a(key)
hash = method b(key)
hash.delete(key)
end
def method_b(key)
return { key => 1 }
end
and then an rspec test like
it 'calls method_b'
expect(someClass).to receive(:method_b).with(key)
method_a(key)
end
However I then get an error in the second line of method_a because it's trying to call delete on a nil object. When I debug, I can see that the logic inside method_b is never actually being invoked. It's not failing somewhere in method_b, it's literally not calling it at all. If I get rid of the expect statement in the test, this error goes away. It seems like the expect statement is causing it to just skip over the actual call to method_b, leaving me with a nil value instead of the hash I'm expecting.
Is there a way I can stop it from skipping over method_b, or at least terminate execution once the expect statement is successful, so I don't run into the error on the next line?
When you set a message expectation, it overrides the original code, unless you explicitly tell RSpec not to:
expect(someClass).to receive(:method_b).with(key).and_call_original

Testing custom exception with a single expect?

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

Alternative to rspec double that does not fail test even if allow receive is not specified for a function

Many times one outcome may have two different consequences that need to be tested with a test double. For example if a network connection is successful I'd like to log a message, and also pass the resource to another object that will store it internally. On the other hand it feels unclean to put these two in one test. For example this code fails:
describe SomeClass do
let(:logger) { double('Logger') }
let(:registry) { double('Registry') }
let(:cut) { SomeClass.new }
let(:player) { Player.new }
describe "#connect" do
context "connection is successful" do
it "should log info" do
logger.should_receive(:info).with('Player connected successfully')
cut.connect player
end
it "should register player" do
registry.should_receive(:register).with(player)
cut.connect player
end
end
end
end
I could specify in each test that the function in the other one might get called, but that looks like unnecessary duplication. In that case I'd rather make this one test.
I also don't like that it's never explicit in the test that a method should NOT be called.
Does anyone know about an alternative that has an explicit 'should_not_receive' message instead of automatically rejecting calls that are not explicitly specified?
RSpec supports should_not_receive, which is equivalent to should_receive(...).exactly(0).times as discussed in this message from the original author of RSpec.

Is there a way to get a stack trace from rspec when a method is unexpectedly called more times than specified?

I setup a mock object and told it to expect a check for nil and to return false:
status = double('status')
status.should_receive(:nil?).and_return(false)
I only expect the call to nil? to occur once, but I got an error in my rspec test, saying that status received nil? twice.
Is there a way to get rspec to show where/how each call occurred?
adding the '--backtrace' option did not work.
Try something like this:
status.should_receive(:nil?).twice { puts caller; false }
This tells rspec to allow two invocations and call the associated block each time. Thecaller method generates a full backtrace which you should be able to analyze onstdout. We also returnfalse to stay on the code-path we're testing.
If the two backtraces are hard to distinguish and you're only interested in the second (unexpected) invocation, then set up two successive expectations:
status.should_receive(:nil?).and_return(false)
status.should_receive(:nil?) { puts caller; false }
Here the double will return false on the first invocation and call the block on the second.
Reference for setting responses on expectations:
https://github.com/rspec/rspec-mocks#setting-responses

Raise custom exceptions, return constants, or return symbols? Why?

Assuming I have a WebCrawler class. There are several errors it can encounter. How should I propagate the errors upward?
Using exceptions:
class WebCrawler
class UrlBadFormatError < StandardError; end
class PageNotFoundError < StandardError; end
class UnauthorizedError < StandardError; end
def crawl(url)
if(! url =~ /some_format/)
raise UrlBadFormatError
response = get(url)
if(response.code == 404)
raise PageNotFoundError
if(response.code == 403)
raise UnauthorizedError
...
end
end
or constants:
class WebCrawler
URL_BAD_FORMAT = 1
PAGE_NOT_FOUND = 2
UNAUTHORZIED = 3
def crawl(url)
if(! url =~ /some_format/)
return URL_BAD_FORMAT
response = get(url)
if(response.code == 404)
return PAGE_NOT_FOUND
if(response.code == 403)
return UNAUTHORZIED
...
end
end
or symbols:
class WebCrawler
def crawl(url)
if(! url =~ /some_format/)
return :url_bad_format
response = get(url)
if(response.code == 404)
return :page_not_found
if(response.code == 403)
return :unauthorized
...
end
end
which is best? or it depends(on what?)
For something which indicates programmer error, such as the wrong type of argument passed to a method, definitely throw an exception. The exception will crash the program, drawing the programmer's attention to the fact that they are using your class incorrectly, so they can fix the problem. In this case, returning an error code wouldn't make sense, because the program will have to include code to check the return value, but after the program is debugged, such errors shouldn't ever happen.
In your WebCrawler class, is it expected that crawl will receive a bad URL as an argument sometimes? I think the answer is probably no. So raising an exception would be appropriate when a bad URL is passed.
When an exception is raised, the flow of execution suddenly "jumps" to the innermost handler. This can be a useful way to structure code when the exception is not expected to happen most of the time, because you can write the "main flow" of your method as simple, straight-line code without including a lot of details about what will happen when some rare error condition occurs. Those details can be separated from the "main flow" code, and put in an exception handler. When an error condition is expected to happen under normal conditions, though, it can be better to put the error handling code inline with the "main flow", to make it clearer what is going on. If the control flow of your program "jumps around" (as is the case when exceptions are used for normal flow control), that means the reader also has to "jump around" in the program text as they are figuring out how it works.
For the other two, I think it is expected that at least sometimes, the HTTP request will return an error code. To determine whether an exception or special return value is the best way to indicate such a condition, I would think about how often those conditions are going to happen under normal usage. Think also about how the client code will read either way. If you use exceptions, they will have to write something like:
urls.map do |url|
begin
crawl(url)
rescue PageNotFoundError
""
rescue UnauthorizedError
""
end
end
(By the way, I think this code example shows something: it might be a good idea if both of your custom exceptions inherit from a common superclass, so you can catch both of them with a single rescue clause if desired.) Or if you use error codes, it would look something like:
urls.map do |url|
response = crawl(url)
if [:page_not_found, :unauthorized].include? response
""
else
response
end
end
Which do you think reads better? It's really up to you. The one thing which you do not want to do is use integer constants for errors. Why use integers? When you print them in a debug trace, you'll have to go look at the list of constants to see what each one means. And using symbols is just as efficient computationally.
Why wouldn't you throw exceptions? They can encapsulate additional information besides just the type, are trivially rescued, and if you're using an IDE, are first-class citizens.
If it's an exception then by all means raises an exception! All three of those cases are, in my opinion, exceptions. While some may argue that 4xx status codes aren't exception-worthy since you may expect them to happen, they are still client errors.
You may also read about Ruby's throw/catch, which offer exception-like behavior for cases where "don't use exceptions for control flow" applies (though I don't think that's the case here).
You should raise errors. If you encounter a malformed URL, or if the page isn't found, or if you weren't authorized to access the page, it means you cannot continue crawling. Raising an error or exception returns from the method and lets the caller deal with the unusual situation.
It should also include information about the error, such as error codes, the URL which resulted in an error and any other relevant information. It can help in deciding how best to handle the error and can later be formatted into a helpful message for the user.
What you should not do, ever, is return numeric error codes. Ruby is not C. Just use symbols instead.
I am against the use of exceptions upon encountering 403s, 404s, malformed urls and similar common occurences on the web. Exceptions are meant for "internal" errors. In the World Wild Web, bad URLs are entirely unexceptional. There should be a method(s) for handling each different URL disease. I would personally return special values as symbols, or some "SpecialCase" objects recording what happened. There is also underused catch...throw statement.

Resources