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

It seems that best idea is to use stub, instead of real rand. This way you would be able to test all values that you are interested in. As rand is defined in Kernel module you should stub it using:

Kernel.stub(:rand).with(anything) { randomized_value }

In particular contexts you can define randomized_value with let method.


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.


I'd extract the random number generation:

def chance
  rand(4)
end

def some_method
  if chance == 1 do
    # logic here
  else
    # another logic here
  end
end

And stub it:

your_instance.stub(:chance) { 1 }

This doesn't tie your test to the implementation details of rand and if you ever decide to use another random number generator, your test doesn't break.