Mocking successive return values

I’m having a problems mocking successive return values. I don’t know if
I’m
doing something wrong or if this is a limitation of rspec mocks. Any
ideas
of what I may be doing wrong?

I’m trying to test the generate_quote_number method. It generates a
quote
number, looks to see if it is in the db already. If it is, it calls
itself
to try again. I want to test that it looks in the database for matching
numbers until it finds one which does not exist already.

The code

def generate_quote_number
quote_num = “MMQ#{self.random_string}-001”
if Policy.find(:first, :conditions => [“quote_number = ?”,
quote_num])
quote_num = generate_quote_number
end
quote_num
end

def random_string(size=8)
# return a random string
end

For the test, I simply lookup 3 existing quote numbers, append nil to
the
end (for a total of 4)

The test

it “should not generate a duplicate quote number” do
existing_quote_numbers = Policy.find(:all).map{|p|
p.quote_number}[0,3]

@policy_service.should_receive(:random_string).with(:no_args).exactly(4).times.and_return(existing_quote_numbers.concat([nil]))
@policy_service.generate_quote_number
end

The result

should not generate a duplicate quote number
Mock ‘Service::Base’ expected :random_string with (no args) 4 times, but
received it once

I verified “and_return” is actually returning the array rather than an
individual element of the existing_quote_numbers array which makes
sense,
but what about this?
http://rspec.rubyforge.org/svn/branches/dogfood/spec/spec/mocks/mock_spec.rb

specify “should use a list of return values for successive calls” do
@mock.should_receive(:multi_call).twice.with(:no_args).and_return([8,
12])
@mock.multi_call.should_equal 8
@mock.multi_call.should_equal 12
@mock.__verify
end

Any feedback on how to properly test this is much appreciated.

Thanks,
Doug

Doug Bryant wrote:

p.quote_number}[0,3]

@policy_service.should_receive(:random_string).with(:no_args).exactly(4).times.and_return(existing_quote_numbers.concat([nil]))
@policy_service.generate_quote_number
end

You will need to have one expectation per return. Try this:

it “should not generate a duplicate quote number” do
#expect
Policy.find(:all).map{|p| p.quote_number}[0,3].concat([nil]).each do
|existing_quote_number|

@policy_service.should_receive(:random_string).with(:no_args).once.and_return(existing_quote_number))
end
#when
@policy_service.generate_quote_number
end

-Ben

Thanks Ben. I appreciate it.
Doug

On Wed, Mar 5, 2008 at 9:30 AM, Doug Bryant
[email protected] wrote:
@policy_service.should_receive(:random_string).with(:no_args).exactly(4).times.and_return(existing_quote_numbers.concat([nil]))

 @policy_service.generate_quote_number

end

The result

should not generate a duplicate quote number
Mock ‘Service::Base’ expected :random_string with (no args) 4 times, but
received it once

I verified “and_return” is actually returning the array rather than an
individual element of the existing_quote_numbers array which makes sense

You need to splat em out:

and_return(*existing_quote_numbers.concat([nil]))

Pat

On Wed, Mar 5, 2008 at 12:06 PM, Corey H. [email protected]
wrote:

Hi, Pat,

Just for my own edification and education, if you have a minute, could you
explain what this means and why you have to do it?

Basically it takes an array and splits it up. Here’s a little irb
session that ought to demonstrate it.

def foo(val); p val; end
=> nil
a = [1,2,3]
=> [1, 2, 3]
foo(a)
[1, 2, 3]
=> nil
foo(*a)
ArgumentError: wrong number of arguments (3 for 1)
from (irb):4:in `foo’
from (irb):4

The method expects one argument. When I pass it an array, everything
is fine because it only has one object. However when I splat the
array, it splits it up into three distinct objects and tries to pass
it in, causing the method to blow up.

In RSpec, you specify multiple values to return by passing in multiple
objects. An array is just one object, so the mock just returns it.
Splat it out and you’ve passed multiple values to and_return, causing
it to return them in succession.

  • is called the splat operator and can be used to collect stuff in
    addition to splitting stuff up. Just google for “ruby splat operator”
    to get lots of info. Also you can define #to_splat on any object.

Pat

On Wed, Mar 5, 2008 at 2:17 PM, Pat M. [email protected] wrote:

You need to splat em out:

and_return(*existing_quote_numbers.concat([nil]))

Pat

Hi, Pat,

Just for my own edification and education, if you have a minute, could
you
explain what this means and why you have to do it?

Thanks,
-Corey

Wow! That is really cool. Thanks for the explanation, Pat.

-Corey