Rspec error in ruby code testing - ruby

Rspec code is
it "calls calculate_word_frequency when created" do
expect_any_instance_of(LineAnalyzer).to receive(:calculate_word_frequency)
LineAnalyzer.new("", 1)
end
Code of class is
def initialize(content,line_number)
#content = content
#line_number = line_number
end
def calculate_word_frequency
h = Hash.new(0)
abc = #content.split(' ')
abc.each { |word| h[word.downcase] += 1 }
sort = h.sort_by {|_key, value| value}.reverse
puts #highest_wf_count = sort.first[1]
a = h.select{|key, hash| hash == #highest_wf_count }
puts #highest_wf_words = a.keys
end
This test gives an error
LineAnalyzer calls calculate_word_frequency when created
Failure/Error: DEFAULT_FAILURE_NOTIFIER = lambda { |failure, _opts| raise failure }
Exactly one instance should have received the following message(s) but didn't: calculate_word_frequency
How I resolve this error.How I pass this test?

I believe you were asking "Why do I get this error message?" and not "Why does my spec not pass?"
The reason you're getting this particular error message is you used expect_any_instance_of in your spec, so RSpec raised the error within its own code rather than in yours essentially because it reached the end of execution without an exception, but without your spy being called either. The important part of the error message is this: Exactly one instance should have received the following message(s) but didn't: calculate_word_frequency. That's why your spec failed; it's just that apparently RSpec decided to give you a far less useful exception and backtrace.
I ran into the same problem with one of my specs today, but it was nothing more serious than a failed expectation. Hopefully this helps clear it up for you.

The entire point of this test is to insure that the constructor invokes the method. It's written very clearly, in a very straight forward way.
If you want the test to pass, modify the constructor so it invokes the method.

Related

NoMethodError: undefined method `chomp' for nil:NilClass while running Ruby Rspec on gets.chomp

This is my first post here. I'm fairly new to Ruby, especially RSpec and have been running into an issue. I have written a method that uses gets.chomp to receive a player input. However I have this method called in another method
def prompt_move
loop do
#move = gets.chomp.to_i
return move if valid_move?(move)
puts "Invalid input. Enter a column number between 1 and 7"
end
end
def valid_move?(move)
#move.is_a?(Integer) && #move.between?(1, 7)
end
def play_round
print_board
prompt_player
#move = prompt_move
end
Here is the code for my RSpec tests:
describe ConnectFour do
subject(:game) { described_class.new }
let(:player){ double(Player) }
describe '#prompt_move' do
context 'move is a valid input' do
before do
allow(game).to receive(:gets).and_return('3\n')
end
it 'returns move and stops the loop' do
error_message = 'Invalid input. Enter a column number between 1 and 7'
expect(game).to_not receive(:puts).with(error_message)
game.prompt_move
end
end
context 'when given one invalid input, then a valid input' do
before do
letter = 'a'
valid_input = '1'
allow(game).to receive(:gets).and_return(letter, valid_input)
end
it 'completes loop and displays error message once' do
error_message = 'Invalid input. Enter a column number between 1 and 7'
expect(game).to receive(:puts).with(error_message).once
game.prompt_move
end
end
end
If I remove the #prompt_move method from #play_round the tests pass without any issue. However when I try to call it from within #play_round it gives me
NoMethodError:
undefined method `chomp' for nil:NilClass
I have been struggling to figure out what is causing this error so any suggestions would be greatly appreciated!
You're executing code in your class file.
new_game = ConnectFour.new
new_game.play_game
This will run every time you load the file, like when you're testing it. It will prompt for input and run gets. What it's getting is the code of the test file (for some rspec reason). That's invalid, so it keeps running gets until eventually there is no more input and gets returns nil.
Remove that from your class file. Code like that should be in a separate file which requires the class.

How to prevent problems with `return` from block when using Ruby `yield`

As every Ruby programmer eventually discovers, calling blocks or procs that contain return statements can be dangerous as this might exit your current context:
def some_method(&_block)
puts 1
yield
# The following line will never be executed in this example
# as the yield is actually a `yield-and-return`.
puts 3
end
def test
some_method do
puts 2
return
end
end
test
# This prints "1\n2\n" instead of "1\n2\n3\n"
In cases you want to be absolutely sure some of your code runs after you called a block or proc, you can use a begin ... ensure construct. But since ensure is also called if there is an exception during yield, it requires a little more work.
I've created a tiny module that deals with this problem in two different ways:
Using safe_yield, it is detected whether the yielded block or proc actually returns using the return keyword. If so, it raises an exception.
unknown_block = proc do
return
end
ReturnSafeYield.safe_yield(unknown_block)
# => Raises a UnexpectedReturnException exception
Using call_then_yield, you can call a block and then make sure that a second block is executed, even if the first block contains a return statement.
unknown_block = proc do
return
end
ReturnSafeYield.call_then_yield(unknown_block) do
# => This line is called even though the above block contains a `return`.
end
I'm considering to create a quick Gem out of this, or is there any built-in solution to prevent quick return from the nested block which I missed?
There is a built-in solution to detect whether a block contains a return statement.
You can use RubyVM::InstructionSequence.disasm to disassemble the block passed in by the user, then search it for throw 1, which represents a return statement.
Here's a sample implementation:
def safe_yield(&block)
if RubyVM::InstructionSequence.disasm(block) =~ /^\d+ throw +1$/
raise LocalJumpError
end
block.call
end
Here's how you might incorporate it into your library:
def library_method(&block)
safe_yield(&block)
puts "library_method succeeded"
rescue LocalJumpError
puts "library_method encountered illegal return but resumed execution"
end
And here's the user experience for a well-behaved and a misbehaving user:
def nice_user_method
library_method { 1 + 1 }
end
nice_user_method
# library_method succeeded
def naughty_user_method
library_method { return false if rand > 0.5 }
end
naughty_user_method
# library_method encountered illegal return but resumed execution
Commentary:
Using raise LocalJumpError/rescue LocalJumpError gets around the issues you encountered when using a blanket ensure.
I chose LocalJumpError because it seems relevant, and because (I think!) there is no possible Ruby code that would result in LocalJumpError being raised "naturally" in this context. If that turns out to be false, you can easily substitute your own new exception class.

Rspec 3.0 How to mock a method replacing the parameter but with no return value?

I've searched a lot and just cannot figure this out although it seems basic. Here's a way simplified example of what I want to do.
Create a simple method that does something but doesn't return anything, such as:
class Test
def test_method(param)
puts param
end
test_method("hello")
end
But in my rspec test I need to pass a different parameter, such as "goodbye" instead of "hello." I know this has to do with stubs and mocks, and I've looking over the documentation but can't figure it out: https://relishapp.com/rspec/rspec-mocks/v/3-0/docs/method-stubs
If I do:
#test = Test.new
allow(#test).to_receive(:test_method).with("goodbye")
it tells me to stub out a default value but I can't figure out how to do it correctly.
Error message:
received :test_method with unexpected arguments
expected: ("hello")
got: ("goodbye")
Please stub a default value first if message might be received with other args as well.
I am using rspec 3.0, and calling something like
#test.stub(:test_method)
is not allowed.
How to set a default value that is explained at
and_call_original can configure a default response that can be overriden for specific args
require 'calculator'
RSpec.describe "and_call_original" do
it "can be overriden for specific arguments using #with" do
allow(Calculator).to receive(:add).and_call_original
allow(Calculator).to receive(:add).with(2, 3).and_return(-5)
expect(Calculator.add(2, 2)).to eq(4)
expect(Calculator.add(2, 3)).to eq(-5)
end
end
Source where I came to know about that can be found at https://makandracards.com/makandra/30543-rspec-only-stub-a-method-when-a-particular-argument-is-passed
For your example, since you don't need to test the actual result of test_method, only that puts gets called in it passing in param, I would just test by setting up the expectation and running the method:
class Test
def test_method(param)
puts param
end
end
describe Test do
let(:test) { Test.new }
it 'says hello via expectation' do
expect(test).to receive(:puts).with('hello')
test.test_method('hello')
end
it 'says goodbye via expectation' do
expect(test).to receive(:puts).with('goodbye')
test.test_method('goodbye')
end
end
What it seems you're attempting to do is set up a test spy on the method, but then I think you're setting up the method stub one level too high (on test_method itself instead of the call to puts inside test_method). If you put the stub on the call to puts, your tests should pass:
describe Test do
let(:test) { Test.new }
it 'says hello using a test spy' do
allow(test).to receive(:puts).with('hello')
test.test_method('hello')
expect(test).to have_received(:puts).with('hello')
end
it 'says goodbye using a test spy' do
allow(test).to receive(:puts).with('goodbye')
test.test_method('goodbye')
expect(test).to have_received(:puts).with('goodbye')
end
end

Does should_receive do something I don't expect?

Consider the following two trivial models:
class Iq
def score
#Some Irrelevant Code
end
end
class Person
def iq_score
Iq.new(self).score #error here
end
end
And the following Rspec test:
describe "#iq_score" do
let(:person) { Person.new }
it "creates an instance of Iq with the person" do
Iq.should_receive(:new).with(person)
Iq.any_instance.stub(:score).and_return(100.0)
person.iq_score
end
end
When I run this test (or, rather, an analogous one), it appears the stub has not worked:
Failure/Error: person.iq_score
NoMethodError:
undefined method `iq_score' for nil:NilClass
The failure, as you might guess, is on the line marked "error here" above. When the should_receive line is commented out, this error disappears. What's going on?
Since RSpec has extended stubber functionality, now following way is correct:
Iq.should_receive(:new).with(person).and_call_original
It will (1) check expectation (2) return control to original function, not just return nil.
You're stubbing away the initializer:
Iq.should_receive(:new).with(person)
returns nil, so Iq.new is nil. To fix, just do this:
Iq.should_receive(:new).with(person).and_return(mock('iq', :iq_score => 34))
person.iq_score.should == 34 // assert it is really the mock you get

Detect Rspec test failure on after each method

I am trying to run an RSpec test, and I want to detect if the test failed in the after method. I have something like this right now:
after(:each) do
cc = ConnectController.new()
cc.update(<TEST-SERVER-CONTROLLER>, <TC-RESULT-ID>, result?)
end
As you can see, the result? function is what I need to replace, to detect if the test fails or not, and to also get information about the test that failed.
In addition to Daniel's answer, in Rspec3 the example method was deleted (see here for more info).
You will have to do something like this:
after(:each) do |example|
if example.exception
# ...
end
end
EDIT: this answer is only valid for RSpec 2. for RSpec 3 see geekazoid's answer.
The after each block runs in the context of class which exposes example and you can detect failures by checking the exception method on example thusly:
after(:each) do
if example.exception != nil
# Failure only code goes here
end
end
I was looking for how to check if success for all examples in a group in a after(:context) / after(:all) block. Here's what I came up with:
after(:all) do |example_group|
all_groups = example_group.class.descendants
failed_examples = all_groups.map(&:examples).flatten.select(&:exception)
if failed_examples.empty?
# runs only if there are no failures
do('something')
end
end

Resources