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')