I'm new to testing with RSpec and FactoryBot so any help would be appreciated. I've encountered an odd case with the following code/tests.
Here are my models:
class Foo < ActiveRecord::Base
has_many :bars, dependent: :destroy
def update_baz_count(baz_count)
most_recent_bar.update_current_baz_count(baz_count)
end
def most_recent_bar
bars.last
end
end
class Bar < ActiveRecord::Base
belongs_to :foo
def update_current_baz_count(new_baz_count)
self.baz_count = new_baz_count
self.save
end
end
And here are my tests:
describe Foo do
# This test passes
describe "#most_recent_bar" do
let!(:foo) { create(:foo) }
let!(:bar) { create(:bar, foo: foo) }
it 'should return the most recent bar' do
expect(foo.most_recent_bar).to eq(bar)
end
end
describe '#update_baz_count' do
let!(:foo) { create(:foo) }
let!(:bar) { create(:bar, foo: foo) }
it 'should call update_current_bar_count on the storage history' do
## Test will NOT pass unless following line is uncommented:
# expect(foo).to receive(:most_recent_bar).and_return(bar)
expect(bar).to receive(:update_current_baz_count).with(1)
foo.update_baz_count(1)
end
end
end
The issue is that in my #update_baz_count
test passing is contingent on setting an expectation regarding the #most_recent_bar
method. As noted above, my test for #most_recent_bar
passes, and it feels redundant to be making assertions about the performance of that method outside of its dedicated test.
So, why is the success of my test contingent on the line expect(foo).to receive(:most_recent_bar).and_return(bar)
?
The problem is that you set up the mocking behavior on the object available in specs:
expect(bar).to receive(:update_current_baz_count).with(1)
But! In your production code, the same row will be fetched from db:
bars.last
And AR will create a new object for you, which have no idea that you mocked it in your specs.
You can check it out like this:
expect(bar.object_id).to eq foo.most_recent_bar.object_id
Which will fail.
If you want to do it without mocking, do something like this:
it 'should update update_current_bar_count on the storage history' do
expect{ foo.update_baz_count(1) }
.to change { bar.reload.field_that_baz_count_updates}.from(0).to(1)
end
So instead of checking that the method was called, check the effect the calling of the method had on the "world".