rspec >= 3.1 brings a method and_wrap_original
. It seems a bit complicated at first, but there are use cases where it helps to write precise tests. For example it allows to add expectations on objects that will only be created when your code is called.
If you have older rspec, you could use expect_any_instance_of
, but with the drawback, that you can't be sure if it really was the correct instance which got the message.
Example
The example model uses different validators based on a flag:
class MyModel < ApplicationRecord
# ...
def enhanced_valid?(skip_expensive_checks: false)
enhanced_errors(skip_expensive_checks: skip_expensive_checks).none?
end
def enhanced_errors(skip_expensive_checks: false)
validator = if skip_expensive_checks
ActiveType.cast(self, SuperValidator)
else
ActiveType.cast(self, SuperDuperValidator)
end
validator.valid?
validator.errors.full_messages
end
end
The spec has to ensure that the correct Validator is used:
context 'when skipping expensive checks' do
let(:record) { MyModel.new }
it 'uses the SuperValidator' do
expect(ActiveType).to receive(:cast) # <- the method (`cast`) is mocked (replaced)
.with(record, SuperValidator) # <- ensure the expected arguments are given
.and_wrap_original do |method, *arguments| # <- here is `and_wrap_original`
validator = method.call(*arguments) # <- the original method (`cast`) is explicitly called
expect(validator).to receive(:valid?) # <- a expectation on the then newly created object is added
validator # <- the new object is returned
end
record.enhanced_valid?(skip_expensive_checks: true)
end
end
Also see here for further examples.
Please note: Usually it is preferrable to test behaviour, not method calls. Since the behaviour of both validators is thoroughly tested in their own specs, mocking in this case avoids unnecessary duplication.