ruby-on-railsrubyunit-testingrspecyield

Ruby, Rspec, and yield stubbing


Let's say I have a class like this in Ruby:

class Test
  def execute
    count = 0
    40.times do
      search_for_names(count) do |name, last_name|
        yield name, last_name
      end
      count += 1
    end
  end

  def search_for_names(count)
    friend = get_friend_name(count)
    yield friend.name, friend.last_name
  end
end

My question is: how do I do to stub my search_for_names method to get 40 different names in my Rspec test? (I installed Faker). I tried:

let(:friends) do
described_class.new
end

allow(friends).to receive(:search_for_names).and_yield(
      Faker::Name.name,
      Faker::Name.last_name
)

 it 'finds multiple friends' do
    friends.execute do |name, last_name|
      puts name
      expect(name).not_to be_empty
      expect(last_name).not_to be_empty
    end
  end

But it prints always the same name x40.

And... :

allow(friends).to receive(:search_for_names).and_yield(
      Faker::Name.name,
      Faker::Name.last_name
    ).and_yield(
      Faker::Name.name,
      Faker::Name.last_name
    )

But it prints two different names x40 (80 names). But I would like to have only 40 times a different name. Is it possible ? Thanks in advance !


Solution

  • The issue is .and_yield(Faker::Name.name, Faker::Name.last_name) is being executed once. Your function is already prepared to receive a param, use it:

    40.times do |i|
      allow(friends).to receive(:search_for_names).with(i).and_yield(
        "#{Faker::Name.name}_#{i}",
        "#{Faker::Name.last_name}_#{i}"
      )
    end
    

    Sidenote: instead of introducing a local variable count, just use what Integer#times passes to the block:

    def execute
      40.times do |count|
        search_for_names(count) do |name, last_name|
          yield name, last_name
        end
      end
    end