RSpec: Matching arguments for receive_message_chain

Here's how we addresses a similar situation:

expect(Theme).to receive(:scoped).and_return(double('scope').tap do |scope| 
  expect(scope).to receive(:includes).with(:categories).and_return scope
  expect(scope).to receive(:where).with(categories: { parent_id: '1'}).and_return scope
  expect(scope).to receive(:order).with('themes.updated_at DESC').and_return scope
  expect(scope).to receive(:offset).with(10).and_return scope
  expect(scope).to receive(:limit).with(10000)
end)

In recent months, by pure accident, I discovered that you can actually chain your "with" invocation in the order of the message chain. Thus, the previous example, becomes this:

expect(Theme).to receive_message_chain(:scoped, :includes, :where, :order, :offset, :limit).with(no_args).with(:categories).with(categories: { parent_id: '1'}).with('themes.updated_at DESC').with(10).with(10000)

It doesn't appear that you can use with in combination with receive_message_chain when the arguments pertain anything other than the final method. Thus the message:

#<Double (anonymous)> received :first with unexpected arguments

This makes sense -- how can RSpec know which method in the chain should receive the arguments?

To verify the argument expectation, don't stub the chain, just stub where

allow(Thing).to receive(:where).with(uuid: 1).and_return([])
expect(Thing.where(uuid: 1).first).to eq nil

Or omit the arguments:

 allow(Thing).to receive_message_chain(:where, :first).and_return(nil)
 expect(Thing.where(uuid: 1).first).to eq nil

receive_message_chain is not recommended IMO. From the docs:

you should consider any use of receive_message_chain a code smell


It's sometimes error prone (I'll intermittently get an error saying "wrong number of arguments (0 for 1+)"; although this seems to only happen when performing multiple receive_message_chains in a single test), but you can also opt for chaining your "with" methods thus:

expect(User).to receive_message_chain(:where, :order).with(active: true).with('name ASC')