I'm looking for best-practice alternatives to expect_any_instance_of
, because the RSpec documentation discourages using expect_any_instance_of
:
This feature is sometimes useful when working with legacy code, though in general we discourage its use for a number of reasons: ... [reference]
I use expect_any_instance_of
a lot in cases where I want to test that a method will be called when certain conditions are met, but where the object will be loaded in a different scope.
For example, when writing a controller spec, I simply want to test that the correct method is called with the correct parameters on an instance of X.
Ok, well. The answer is - it depends :)
Here are some things, that may help you:
1) Have a look at the way you are testing code. There are (in general) two ways to do it.
Suppose you have this class:
class UserUpdater
def update(user)
user.update_attributes(updated: true)
end
end
Then you can test it in two ways:
Stub everything:
it 'test it' do
user = double(:user, update_attributes: true)
expect(user).to receive(:update_attributes).with(updated: true)
UserUpdater.new.update(user)
end
Minimal (or no) stubbing:
let(:user) { FactoryGirl.create(:user) }
let(:update) { UserUpdater.new.update(user) }
it { expect { update }.to change { user.reload.updated }.to(true) }
I prefer the second way - because it is more natural and gives me much more confidence in my tests.
Back to your example - are you sure that you want to check the method call when the controller action runs? In my opinion - it is better to check the result. Everything behind it should be tested separately - for example, if your controller has a service called - you will test everything about this service in it's own spec, and how the action works in general (some kind of integration tests) in the controller spec.
2. Check what is returned, not how it works:
For example, you have a service, which can find or build a user for you:
class CoolUserFinder
def initialize(email)
@email = email
end
def find_or_initialize
find || initialize
end
private
def find
User.find_by(email: email, role: 'cool_guy')
end
def initialize
user = User.new(email: email)
user.maybe_cool_guy!
user
end
end
And you can test it without stubbing on any instance:
let(:service) { described_class.new(email) }
let(:email) { '[email protected]' }
let(:user) { service.find_or_initialize }
context 'when user does not exist' do
it { expect(user).to be_a User }
it { expect(user).to be_new_record }
it { expect(user.email).to eq '[email protected]' }
it { expect(user.role).to eq 'maybe_cool_guy' }
it { expect(user).to be_on_hold }
end
context 'when user already exists' do
let!(:old_user) { create :user, email: email }
it { expect(user).to be_a User }
it { expect(user).not_to be_new_record }
it { expect(user).to eq old_user }
it { expect(user.role).to eq 'cool_guy' }
it { expect(user).not_to be_on_hold }
end
3. And finally sometimes you REALLY need to stub any instance. And it's ok - sometimes shit happens :)
Sometimes you can also replace any_instance with stub like this:
allow(File).to receive(:open).and_return(my_file_double)
I hope it will help you a bit and I hope it is not too long :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With