9

I'm using Ruby 2.3.4 and rspec 3.6.0.

I'm writing a test for an object that uses rand(10000..99999). I can't find any docs on rand to see what object it's a part of. I tried stubbing Kernel, Object, and Random (see below) but none of my attempts resulted in rand being stubbed for the object.

allow(Kernel).to receive(rand).and_return(12345)
allow(Object).to receive(rand).and_return(12345)
allow(Random).to receive(rand).and_return(12345)

Any help is appreciated.

Holger Just
  • 52,918
  • 14
  • 115
  • 123
hummmingbear
  • 2,294
  • 5
  • 25
  • 42

3 Answers3

13

rand is indeed implemented in the Kernel module. However, when calling the method inside your code, the method receiver is actually your own object.

Assume the following class:

class MyRandom
  def random
    rand(10000..99999)
  end
end

my_random = MyRandom.new
my_random.random
# => 56789

When calling my_random.random, the receiver (i.e. the object on which the method is called on) of the rand method is again the my_random instance since this is the object being self in the MyRandom#random method.

When testing this, you can this stub the rand method on this instance:

describe MyRandom do
  let(:subject) { described_class.new }

  describe '#random' do
    it 'calls rand' do
      expect(subject).to receive(:rand).and_return(12345)
      expect(subject.random).to eq 12345
    end
  end
end
Holger Just
  • 52,918
  • 14
  • 115
  • 123
  • Interesting, I had no idea ruby assigns `rand` called on it's own to the object it's called. This worked, thank you!! – hummmingbear Aug 18 '17 at 17:23
  • This is the same for all modules. If you include the module in a class, it's the methods are called on the class instance. `Kernel` is no different here. It's only "special" in that the `Kernel` module is included in all Objects by default, allowing its methods to be called everywhere. Since it is still a normal module, you can however overwrite its methods per class the same way you could for any other methods (although you should be careful doing so). – Holger Just Aug 18 '17 at 18:08
6

This works:

allow_any_instance_of(Object).to receive(:rand).and_return(12345)
Jerome Dalbert
  • 10,067
  • 6
  • 56
  • 64
0

Sometimes it can be hard to stub objects that are deep within another. So I find this approach helps simplify things:

class Worker
  def self.rand(*args)
    # Calls the actual rand method
    Kernel.rand(*args)
  end

  def perform
    # Calls private method rand -> Worker.rand
    rand(1..5)
  end

  private

  def rand(*args)
    self.class.rand(*args)
  end
end

This allows us to stub with ease:

allow(Worker).to receive(:rand).and_return(2)

expect(Worker.new.perform).to eq 2
axsuul
  • 7,370
  • 9
  • 54
  • 71