I have a unit test that relies on a random dice roll. I roll a 20 sided die and if the value is 20 it counts as a critical hit.
What I’m doing right now is rolling the 20 sided die up to 300 times. If any one of those rolls is a 20 then I know I had a critical hit.
Here’s what the code looks like:
public class DiceRoll
{
public int Value { get; set; }
public bool IsCritical { get; set; }
// code here that sets IsCritical to true if "Value" gets set to 20
}
[Test]
public void DiceCanRollCriticalStrikes()
{
bool IsSuccessful = false;
DiceRoll diceRoll = new DiceRoll();
for(int i=0; i<300; i++)
{
diceRoll.Value = Dice.Roll(1, 20); // roll 20 sided die once
if(diceRoll.Value == 20 && diceRoll.IsCritical)
{
IsSuccessful = true;
break;
}
}
if(IsSuccessful)
// test passed
else
// test failed
}
Although the test does exactly what I want it to I can’t help but feel like I’m doing something wrong.
On a related note, the DiceRoll class has other information in it as well but my question is specifically about looping in a unit test, so I left it out to make it more clear
The problem with this approach is that your are relying on a random behaviour. There is a possiblity that within the 300 rolls, the wanted state never appears, and the unit test fails, without the tested code being wrong.
I would look into extracting the dice roll logic out from the Dice class through an interface (“IDiceRoller” for instance). Then you can implement the random dice roller in your application, and another dice roller in your unit test project. This one will could always return a predefined value. This way you can write tests for specific dice values without having to resort to looping and hoping for the value to show up.
Example:
(code in your application)
…and in your unit test project:
Now, in your unit test you can create a
MockedDiceRoller, set the value you want the dice to get, set the mocked dice roller in theDiceclass, roll and verify that behaviour: