Mocking Chain of Methods in Rspec

How do I 'expect' a chain of methods using Rspec where the first method takes a parameter?

Generally speaking, I prefer not to use stub chains, as they are often a sign that you are violating the Law of Demeter. But, if I had to, this is how I would mock that sequence:

let(:vanity_url) { 'https://vanity.url' }
let(:partner_campaigns) { double('partner_campaigns') }
let(:loaded_partner_campaigns) { double('loaded_partner_campaigns') }

let(:partner_campaign) do
double("Contentful::Model", fields {:promotion_type => "Promotion 1"}
end

before do
allow(Contentful::PartnerCampaign)
.to receive(:find_by)
.with(vanity_url: vanity_url)
.and_return(partner_campaigns)

allow(partner_campaigns)
.to receive(:load)
.and_return(loaded_partner_campaigns)

allow(loaded_partner_campaigns)
.to receive(:first)
.and_return(partner_campaign)
end

Stubbing Chained Methods with Rspec

I figured something out.

Client.stub!(:named_scope).and_return(@clients = mock([Client]))
@clients.stub!(:first).and_return(@client = mock(Client))

which allows me to call my controller:

@client = Client.named_scope(param).first

It works, but is there a better solution?

EDIT:

The release of rspec 1.2.6 allows us to use stub_chain meaning it can now be:

Client.stub_chain(:named_scope, :chained_call).and_return(@clients = [mock(Client)])

This was top of my head, as always check the api for specifics :)

RSpec: Stub chains with arguments?

You can use this:

Payment.stub_chain(:order, :where).with(:updated_at).with(:paid => true) { return_this }

How to stub two chained ActiveRecord methods in RSpec?

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!

RSpec: how to chain receive().with()?

IMHO you have to go the long way. There is no shorter way to describe it.

Regardless of that, I would recommend you overthink your testing strategy. At the moment you test that a very specific combination of methods is called but not if these method calls are actually doing what you want them to do.

Instead, I would create an example record that should be deleted (and perhaps a couple that should not be deleted), then run the job and afterward test that only the expected record was deleted.

For example like this:

let!(:record_to_be_deleted) { 
Delayed::Job.create!(queue: 'produceable', locked_at: nil)
}
let!(:records_to_stay) do
[
Delayed::Job.create!(queue: 'produceable', locked_at: Time.current),
Delayed::Job.create!(queue: 'default', locked_at: nil)
]
end

it "should remove only expected records" do
expect {
instance.run_produceable_job
}.to chance { Delayed::Job.count }.from(3).to(2)

expect {
record_to_be_deleted.reload
}.to raise_error(ActiveRecord::RecordNotFound)
end

The rule of thumb is to test the expected outcome, not the specific implementation. Because the implementation might change, will be refactored or might break in future versions.



Related Topics



Leave a reply



Submit