Skip to content

Instantly share code, notes, and snippets.

@antillas21
Last active April 22, 2024 16:56
Show Gist options
  • Save antillas21/11232600 to your computer and use it in GitHub Desktop.
Save antillas21/11232600 to your computer and use it in GitHub Desktop.
Stubbing an ActiveRecord::Relation object
# first we create an empty ActiveRecord::Relation object
relation = Model.where(attribute: value)
# then we make the relation stub the :[] method
# and return whatever values we need
relation.stub(:[]).and_return( Model.new({attrs: values})] )
# of course, we can make this too
# instead of the step above
record = double("Model", :foo => 'bar', :bar => 'baz')
relation.stub(:[]).and_return([record])
# last, we make our Model class stub the :where method
# and return the relation object we created
Model.stub(:where).and_return(relation)
# additionally we can have use case specific behavior
model_instance.stub(:method_or_scope_that_returns_collection) { relation }
@keesbriggs
Copy link

Line 6 is missing a square brace.

@upstartjohnvandivier
Copy link

upstartjohnvandivier commented May 31, 2022

updated syntax like:

        mocks = [fake_instance_1, fake_instance_2]
        mock_relation = Model.where(id: 1)
        allow(mock_relation).to receive(:[]).and_return(mocks)
        allow(mock_relation).to receive(:where).and_return(mock_relation)

@GabeMillikan
Copy link

GabeMillikan commented Nov 16, 2022

This simply doesn't work - accessing relation[0] will return an array of records, rather than a single record. Furthermore, .any?, .first, .last, etc. are completely nonfunctional.

The correct way to do this is

mocked_relation = Model.all
mocked_relation.stub(:records).and_return(my_custom_array_of_records)

allow(my_instance).to receive(:my_method).and_return(mocked_relation)

Furthermore you can mock a has_many relation (which was my use case) by doing

allow(record).to receive(:related_records).and_wrap_original do |original, *args, &block|
  relation = original.call(*args, &block)
  relation.stub(:records).and_return(my_custom_array_of_related_records)
  relation
end

@gabriel-a-muller
Copy link

gabriel-a-muller commented Jun 27, 2023

Furthermore you can mock a has_many relation (which was my use case) by doing

allow(record).to receive(:related_records).and_wrap_original do |original, *args, &block|
  relation = original.call(*args, &block)
  relation.stub(:records).and_return(my_custom_array_of_related_records)
  relation
end

I wonder why your code didn't work in my particular case. Could it be related to my call of relation = original.call(*args, &block) always returning a nil object?
relation.stub(:records) raises unknown #stub method for #<ActiveRecord::Associations::CollectionProxy []>

But I found a way out of this, not sure if ideal, though:

allow(record).to receive(:related_records).and_wrap_original do |original, *args, &block|
  relation = original.call(*args, &block)
  relation << custom_records
  allow(relation).to receive(:scope_of_related_class).and_return(scoped_custom_records)
  relation
end

Mocking only the has_many return was insufficient for my case because I also needed to mock the scope call.
record.related_records.scope_of_related_class

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment