blog.petitviolet.net

allow_any_instance_of to count up number of method calls behind the scene

2021-08-25

Ruby

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.

https://github.com/rspec/rspec-mocks#settings-mocks-or-stubs-on-any-instance-of-a-class

Problem

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
  end
end

Then, write a small spec.

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

This spec fails due to the following error.

Failure/Error: Hoge.new.hoge
  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 }

  Hoge.new.hoge
  Hoge.new.hoge
  Hoge.new.hoge
  expect(n).to eq(3)
end

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 }

  Hoge.new.hoge
  Hoge.new.hoge
  Hoge.new.hoge
  expect(count).to eq(3)
end

You can see the entire source code at Gist: https://gist.github.com/petitviolet/9953af0aa561ea49c5e2aa13dd52f2ef