allow_any_instance_of to count up number of method calls behind the scene



Rspec is one of the most popular test framework in Ruby, and of course it offers lots of functionalities for being able to write various type of test cases, like mock, stub, matcher, etc. allow_any_instance_of is a powerful method, which is able to stub any instance of a class even though using it is not encouraged.


The problem I want to solve is counting up the number of calls of a method. I thought it can be done easily with .exactly(N).times matcher, but it can’t.

When you have Hoge class with hoge method.

class Hoge
  def hoge

Then, write a small spec.

it 'fails with exactly matcher' do
  expect_any_instance_of(Hoge).to receive(:hoge).exactly(3).times

This spec fails due to the following error.

  The message 'hoge' was received by #<Hoge:1640 > but has already been received by #<Hoge:0x00007fce52827968>

The reason of this failure is that expect_any_instance_of is to set an expectation on an instance. It means it can’t set expectations across more than one instances.

How to solve

To avoid the error, give a proc to count up method calls locally.

it 'succeeds with giving proc to count up' do
  n = 0
  allow_any_instance_of(Hoge).to receive(:hoge) { n += 1 }
  expect(n).to eq(3)

This should succeed.

In addition, you might think why I didn’t use let to declare a variable within a spec, however, let doesn’t work expectedly in this case because let actually declare not a variable but a function. Thus, the following snippet returns undefined method '+' for nil:NilClass error since it attempts to use n as a variable which is defined but not a variable

let(:n) { 0 }

it 'succeeds with giving proc to count up' do
  allow_any_instance_of(Hoge).to receive(:hoge) { n += 1 }
  expect(count).to eq(3)

You can see the entire source code at Gist: