Jwan622 Jwan622 - 3 months ago 32
Ruby Question

How to stub two chained ActiveRecord methods in RSpec?

I have a piece of code like this:

Green::Trees.where(id: ids).find_each do |tree|
tree.grow!
end


What's a good way to stub
find_each
so that in each iteration, it returns a tree that I define in a let block in a test like this:

let(:tree) { create(:tree) }
let(:tree_ids) { [tree.id] }


What can I do?

Should I do something like this?

allow(Green::Tree).to receive(:where).and_return(Green::Tree)
allow(Green::Tree).to receive(:find_each).and_yield(tree)

Answer

RSpec gives you two ways to stub chained method calls.

The succinct way is receive_message_chain:

allow(Green::Trees).to receive_message_chain(:where, :find_each).and_yield(tree)

That doesn't let you specify arguments, however, although it often isn't important to do so.

If you care about arguments, you can do it like this:

results = double
allow(Green::Tree).to receive(:where).with(id: ids).and_return(results)
allow(results).to receive(:find_each).and_yield(tree)

What you wrote would work, but it's incorrect since where doesn't return the class Green::Tree, but an ActiveRecord relation. Green::Tree does implement find_each, but it's a different method with the same name as the one on the relation. Very confusing!

Comments