How to Test 'Rand()' with Rspec

How do I test `rand()` with RSpec?

There are two approaches I would consider:

Approach 1:

Use a known value of seed in srand( seed ) in a before :each block:

before :each do
srand(67809)
end

This works across Ruby versions, and gives you control in case you want to cover particular combinations. I use this approach a lot - thinking about it, that's because the code I was testing uses rand() primarily as a data source, and only secondarily (if at all) for branching. Also it gets called a lot, so exerting call-by-call control over returned values would be counter-productive, I would end up shovelling in lots of test data that "looked random", probably generating it in the first place by calling rand()!

You may wish to call your method multiple times in at least one test scenario to ensure you have reasonable coverage of combinations.

Approach 2:

If you have branch points due to values output from rand() and your assertions are of the type "if it chooses X, then Y should happen", then it is also reasonable in the same test suite to mock out rand( n ) with something that returns the values you want to make assertions about:

 require 'mocha/setup'

Kernel.expects(:rand).with(4).returns(1)
# Now run your test of specific branch

In essence these are both "white box" test approaches, they both require you to know that your routine uses rand() internally.

A "black box" test is much harder - you would need to assert that behaviour is statistically OK, and you would also need to accept a very wide range of possibilities since valid random behaviour could cause phantom test failures.

How to stub rand in rspec?

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

Using stub in rspec to test output of a rand

Based on your example, you want to test the behavior of chance not the implementation.

describe Weather do
it 'returns good' do
weather = Weather.new
allow(weather).to receive(:chance).and_return(0.8)
expect(weather.conditions).to eq :good
end
end

Testing for random values with Rspec

You can do this in your code:

Kernel.rand(Intention.count)

and in your spec:

let(:intention_count) { 3 }

Intention.stub(:count).and_return(intention_count)
Kernel.stub(:rand).with(intention_count).and_return(0) # will return 0

Basically, we are calling rand using the Kernel class to be able to stub that method to return what we want.

Testing in Rspec a random weather methods functionality depending on the random number generated

You have 3 options:

  • wrap randomness in a method and stub this method
  • wrap or a generator class that is injected, and inject a non-random generator in spec
  • stub rand

I prefer the second option, but the first one may be enough.

Private method

class Weather
attr_reader :status, :random_number

def random_weather
@random_number = generate_random_number
# ...
end

def generate_random_number
rand(1..10)
end
end

#specs
it 'Can return sunny when randomly choosing weather' do
subject.stub(:generate_random_number) {5}
expect(subject.status).to eq "Sunny"
end

Generator injection

class Weather
attr_reader :status, :random_number
def initialize(random_number_generator: RandomNumberGenerator)
@random_number_generator = random_number_generator
end

def random_weather
@random_number = random_number_generator.call
# ...
end
end

class RandomNumberGenerator
def self.call
rand(1..10)
end
end

#specs
subject { described_class.new(generator) }
let(:generator) { ->{ 5 } }
it 'Can return sunny when randomly choosing weather' do
expect(subject.status).to eq "Sunny"
end

Stubbing rand in specs

  it 'Can return sunny when randomly choosing weather' do
stub(:rand) {5}
expect(subject.status).to eq "Sunny"
end

How do I take into account the random number in my rspec tests?

You would need to stub the weather_rand method to return a known value to match what you are trying to test.

https://www.relishapp.com/rspec/rspec-mocks/v/2-14/docs/method-stubs

For example:

it 'a plane cannot take off when there is a storm brewing' do
airport = Airport.new [plane]
airport.stub(:weather_rand).and_return(5)
expect(lambda { airport.plane_take_off(plane) }).to raise_error(RuntimeError)
end


Related Topics



Leave a reply



Submit