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.