39

I was wondering how to test a find_each call in rspec. I'm used to simply stubbing what I want my models to return so I don't rely on test data in the db like this:

MyClass.stub(:find).and_return(my_mock)

However, in another class I'm doing this:

MyClass.find_each do |instance_of_my_class|
  do_stuff_here_on(instance_of_my_class)
end

I find that if I do this:

MyClass.stub(:find_each).and_return([one_mock, two_mock])

in the spec test, the "do stuff here" part is not being executed. Does anyone know how to stub a find_each for rspec testing?

Aaron
  • 13,349
  • 11
  • 66
  • 105

4 Answers4

53

You can use and_yield to make rspec call the block passed to the mock:

MyClass.stub(:find_each).and_yield(one_mock).and_yield(two_mock)
John Bachir
  • 22,495
  • 29
  • 154
  • 227
Iain
  • 4,203
  • 23
  • 21
  • Doing this gives the following error yielded |my mocks in here| to block with arity of 1. If I pass the mocks in as an array it doesn't have a problem but the instance_of_my_class in the find_each block is the array and not the elements in it. – Aaron Dec 08 '10 at 16:13
  • 4
    How about `.and_yield(one_mock).and_yield(two_mock)`? – zetetic Dec 08 '10 at 20:38
  • you are quite right, zetetic - I will make sure to test my advice next time :) I've edited my answer to include your correction. – Iain Dec 09 '10 at 10:50
  • Thanks for the correction lain and zetetic! This worked correctly. I just got a book on rspec, but I find there's little useful information on the API on the web in general. Do you have any sources you recommend? – Aaron Dec 09 '10 at 15:39
  • This worked well for mocking Find.find("/somepath") { block to test } – Amir Jan 05 '11 at 15:49
3

If you need to stub find_each on a verified double and have it loop through a specific array of values, you can do this:

let(:my_relation_with_mocked_find_each) do
  relation = instance_double('YourModel::ActiveRecord_Relation')

  receive_yield = receive(:find_each)

  fake_objs.each do |obj|
    receive_yield = receive_yield.and_yield(obj)
  end

  allow(relation).to receive_yield
  relation
end
dermotbrennan
  • 89
  • 1
  • 3
2

The whole point of stubbing a method is so that the method returns an expected value and not execute its contents. If you have a bunch of logic within the find_each method, I would recommend moving it to a separate method and testing that logic separately. You can then test that your method is called during execution.

Here's a pretty high level example:

class Example1
  def my_method
    # some logic
  end
end

class Example2
  def my_other_method
    Example1.find_each(&:my_method)
  end
end

Rspec:

describe Example1 do
  it "should return something" do
    example = Example1.new
    example.my_method.should == something
  end
end

describe Example2 do
  it "should call my_method on Example1" do
    example1 = mock(:example1, :my_method => true)
    example2 = Example2.new

    example1.should_receive(:my_method)
    example2.my_other_method
  end
end
Peter Brown
  • 50,956
  • 18
  • 113
  • 146
  • I'm actually doing the logic in another method that I'm testing. I've updated the find_each block in my question to reflect this. I'm more trying to test that it performs this on all of the mocks I pass in. – Aaron Dec 08 '10 at 16:16
1

This should do it:

MyClass.stub(:find_each) {|block|
  block.call
  [one_mock, two_mock]
}

If do_stuff_here_on isn't globally reachable, e.g. an instance method on some_object, you'll need some instance_eval to get the right scope for the block:

MyClass.stub(:find_each) {|block|
  some_object.instance_eval(&block)
  [one_mock, two_mock]
}
Anders Kindberg
  • 865
  • 10
  • 7