Mocha expected at most once, invoked twice, but method is clearly invoked only once - ruby

I'm using Mocha for mock testing. Below is the relevant code:
# test_player.rb
should "not download the ppg more than once for a given year" do
#durant.expects(:fetch_points_per_game).at_most_once
ppg = #durant.points_per_game
ppg2= #durant.points_per_game
assert_equal ppg, ppg2, "A player should have a points per game"
end
# player.rb
class Player
# ...
def points_per_game(year=Date.today.year)
#points_per_game ||= fetch_points_per_game(year)
end
alias_method :ppg, :points_per_game
private
def fetch_points_per_game(year=Date.today.year)
31.2
end
end
The test failed, complaining that there was an "unexpected invocation: #.fetch_points_per_game(any_parameters)"
My understanding of my code is if #point_per_game is nil, fetch_points_per_game will be called, otherwise, the result is cached for future calls to points_per_game. So why is the test complaining that fetch_points_per_game was called twice?

In your expectation, you're not specifying a return value, so the stubbed call returns nil. This is why it's being called a second time. If you change the expectation to:
#durant.expects(:fetch_points_per_game).at_most_once.returns(1.23)
You should find that the tests now pass.

Related

RSpec - how to test if object sends messages to self in #initialize

After reading this question I really do not like the answer.
Rails / RSpec: How to test #initialize method?
Maybe I am having a third scenario. This is what I have now, inspired by second code from that answer.
# Picture is collection of SinglePictures with same name and filename,
# but different dimensions
class Picture
attr_accessor :name, :filename
attr_reader :single_pics, :largest_width
def initialize(name, filename, dimensions=nil)
#largest_width = 0
#single_pics = {}
add_single_pics(dimensions) if dimensions
end
def add_single_pics(max_dimension)
# logic
end
end
describe '#initialize' do
it 'should not call add_single_pics if dimensions is not given' do
subject = Picture.new('Test Picture', 'Test-Picture')
expect(subject.largest_width).to eq 0
end
it 'should call add_single_pics if dimensions are given' do
subject = Picture.new('Test Picture', 'Test-Picture', 1920)
expect(subject.largest_width).to eq 1920
end
end
I really don't like this because I am testing the functionality of add_single_pics in #initialize tests. I would like to write somehow this in spec:
expect(subject).not_to have_received(:add_single_pics)
expect(subject).to have_received(:add_single_pics)
But I get
Expected to have received add_single_pics, but that object is not a spy
or method has not been stubbed.
Can I fix this somehow?
Spies are an alternate type of test double that support this pattern
by allowing you to expect that a message has been received after the
fact, using have_received.
https://relishapp.com/rspec/rspec-mocks/v/3-5/docs/basics/spies
Only spy object can store the method calls. To test your real class in the way that you want, you have to use expect_any_instance_of statement before the class will be initialized:
expect_any_instance_of(Picture).to receive(:add_single_pics)
Picture.new('Test Picture', 'Test-Picture')
In this case your add_single_pics method will be called, but its logic will not be run, if you need to run it you need to call the and_call_original method on the matcher:
expect_any_instance_of(Picture).to receive(:add_single_pics).and_call_original

Rspec error in ruby code testing

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.

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

How display failure on undescribed example in RSpec?

I am describing class on RSpec
class Pupil
def initialize(name, dateOfBirth)
#name = name
#dateOfBirth = dateOfBirth
end
def name
#name
end
def ages
#should calculate ages
end
end
describe Pupil do
context do
pupil = Pupil.new("Stanislav Majewski", "1 april 1999")
it "should returns name" do
pupil.name.should eq("Stanislav Majewski")
end
it "should calculates ages" do
#not described
end
end
end
RSpec returns:
..
Finished in 0.00203 seconds
2 examples, 0 failures
Is there an elegant way to display a failure message that the method is not described?
If you're concerned that you'll create a test and forget to put anything it in (sometimes I'll create three tests I know I'll need, and work on each of them in turn) then you can do the following:
it "should calculates ages" do
fail
end
OR
it "should calculates ages"
...and that's all (no block) will mark the test as pending automatically. In other words, don't fill out your tests until they have actual test code in them.
Also, if you don't test any assertions (i.e. if your spec doesn't contain any lines that have a call to should in them), your spec will appear to pass. This has happened to me a few times, where I write a new test, expecting it to fail, and it doesn't because I forgot to include the call to should which is what actually tests the assertion.

Resources