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

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

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)

Local variable became nil in an ensure clause

I've stumbled across a strange behaviour I can't explain. Executing the method
def with_baton
until (baton = Baton.obtain)
sleep(2)
end
result = yield
ensure
baton.release
result
end
which makes sure the block we pass to it doesn't execute in parallel by multiple workers, I sometimes receive the following error:
NoMethodError: undefined method `release' for nil:NilClass
lib/*hidden*/common.rb:172 in ensure in with_baton
lib/*hidden*/common.rb:173 in with_baton
lib/*hidden*/common.rb:7 in get
If the execution went past until it means baton is set. How could it happen that its nil in ensure? By the way, the block executes for about five seconds, and I use Ruby MRI 1.9.3-p547.
your Baton.obtain may raise an error, and in that case baton will be nil
call baton.release if baton

Rspec test - keep executing a method until the error message is no longer raised

I'm new to Rspec, so sorry if this is a bad question. In one of the test tests I'm running, I have code which uses a random number generator to determine whether or not a method should be executed. If it cannot be executed, the method raises an error message.
So I need to write a test which continually runs the method on a small array of class objects until it no longer receives the error message. So in effect each class object will eventually successfully execute that method after a few tries.
The array has 6 items. I'm hoping that I need to loop through each one and then use a while loop which then tests whether the error message has been executed, but I haven't got a clue how. Any help gratefully appreciated.
I have something like this at the moment...
def create_planes
6.times do
plane=Plane.new
planes<<plane
end
end
it 'should land each plane' do
create_planes
i = 0
while i<planes.count
begin
airport.plane_land(planes[i])
i++
rescue
next
end
end
expect(airport.plane_count).to eq(6)
end
Generally with RSpec, you'll set up your inputs and test your outputs. You wouldn't execute until an error occurs, you'd execute a known number of lands and then check that the airport's plane changed to what you expect.
it "should maintain a list of landed planes" do
expect {
3.times { airport.plane_land Plane.new }
}.to change { airport.plane_count }.from(0).to(3)
end

Is there any way to set an RSpec expectation to receive a method call as an argument?

Ie
expect(my_class).to receive(:method_b).with(:calling_method_b_here)
(it's not the return value I want, but specifically to test that method_b is called in this way)
?
This should work for you if I understand correctly:
it "should call #method_b when I call #method_that_calls_b" do
expect(my_class).to receive(:method_b).with(anything) #anything means I don't care explictly what is sent in just that it was called
my_class.method_that_calls_b
end
This will test that method_that_calls_b actually does call method_b with any arguments. If you know what arguments it is supposed to receive then replace anything with what you expect it to be called with. e.g.
it "should call #method_b with 'hello'" do
expect(my_class).to receive(:method_b).with('hello')
my_class.method_b('goodbye') #this will fail
my_class.method_b('hello') #this will pass without the above line
end

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

Resources