Reputation: 571
Is there a way to mock a method once and only once per expectation before replacing the original mocked method?
I thought something like this would work (notice the once
)
class Klass
def self.meth
'baz'
end
end
describe Klass do
subject{ described_class.meth }
before{ allow(described_class).to receive(:meth).once.and_return('foo') }
it{ is_expected.to eq 'foo' }
context 'throwing in a context just to test' do
it{ is_expected.to eq 'foo' }
it{ is_expected.to eq 'foo' }
it{ is_expected.to eq 'foo' }
it 'only mocks once' do
expect(subject).to eq 'foo'
expect(subject).to eq 'baz' # this is the key
end # pass
end
end
Unfortunately I get this error:
(Klass (class)).meth(no args)
expected: 1 time with any arguments
received: 2 times
I would have expected to have got that failure if I said expect(Klass).to receive(:meth).once
rather than the more lenient allow
.
I'm wondering how I can mock once and only once per expectation.
Upvotes: 4
Views: 10066
Reputation: 16793
It's perhaps a bit unintuitive, but you can do this by specifying different return values for multiple calls of Klass.meth
.
In your case, you can stub the first call to Klass.meth
with 'foo'
, and then stub every other call to Klass.meth
with the original implementation of the method. That looks like this:
allow(described_class).to receive(:meth).and_return('foo', described_class.meth)
The next thing we'd need to change in your test is to not use subject
in the final test, because it is memoising the value returned when Klass.meth
gets called the first time (which is why all the other tests that use subject
will still pass), and hence making the second expectation in the it 'only mocks once'
test fail. Instead, we can just call the method directly in each spec:
class Klass
def self.meth
'baz'
end
end
describe Klass do
subject { described_class.meth }
before do
allow(described_class).to \
receive(:meth).and_return('foo', described_class.meth)
end
it { is_expected.to eq 'foo' }
context 'throwing in a context just to test' do
it { is_expected.to eq 'foo' }
it { is_expected.to eq 'foo' }
it { is_expected.to eq 'foo' }
it 'only mocks once' do
expect(described_class.meth).to eq 'foo'
expect(described_class.meth).to eq 'baz'
end # pass
end
end
Upvotes: 8