0

Here is my constructor

    public Dice(final int numOfDice)
    {   
        if(numOfDice < Skunk.LOWEST_NUM_OF_DICE && numOfDice > Skunk.HIGHEST_NUM_OF_DICE)
        {
            throw new IllegalArgumentException("the number of dice must be " + Skunk.LOWEST_NUM_OF_DICE + " or " + Skunk.HIGHEST_NUM_OF_DICE);
        }
        
        this.numOfDice = numOfDice;
        
        dice = new int[this.numOfDice];
        
        Random  r;
        r = new Random();
                
        for(int i=0; i<this.numOfDice; i++)
        {
            dice[i] = r.nextInt(HIGHEST_NUMBER_ON_DIE-LOWEST_NUMBER_ON_DIE+OFFSET) + LOWEST_NUMBER_ON_DIE;
        }
    }

I want to be able to write a unit test where I initialize the dice to specific values. However, I can't seem to figure out how I can control the Random class. I tried @Override on the constructor but it only works for functions. I tried moving the for loop to a method called roll and using @Override on it but @Override doesn't work on functions called in the constructor.

I have installed Mockito and am trying to follow the advice posted in the first answer here: How to test a method that uses Random(), without arguments and return value, using JUnit or Mockito But I don't know how Mockito works.

2 Answers2

0

Make the Random object a field and mock it:

private Random random = new Random();

public Dice(final int numOfDice)
{   
    if(numOfDice < Skunk.LOWEST_NUM_OF_DICE && numOfDice > Skunk.HIGHEST_NUM_OF_DICE)
    {
        throw new IllegalArgumentException("the number of dice must be " + Skunk.LOWEST_NUM_OF_DICE + " or " + Skunk.HIGHEST_NUM_OF_DICE);
    }
    
    this.numOfDice = numOfDice;
    
    dice = new int[this.numOfDice];
    for(int i=0; i<this.numOfDice; i++)
    {
        dice[i] = random.nextInt(HIGHEST_NUMBER_ON_DIE-LOWEST_NUMBER_ON_DIE+OFFSET) + LOWEST_NUMBER_ON_DIE;
    }
}

Replace random with a mock for your tests.

Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • Thanks! Two questions: (1) Aren't you supposed to initialize instance variables in the constructor? So I could have private Random random; above and random = new Random() in the constructor. (2) how do you mock Random? If you can show me or provide a good reference, that would be great. I am new to Mockito. – bluetooth12 Jun 28 '20 at 01:14
  • For instance, if I wanted to set my dice to 1-5-1, how would I do that? – bluetooth12 Jun 28 '20 at 01:20
  • There is no such “rule” that says you must assign fields in the constructor - you can assign fields in the declaration as you like. You shouldn’t have anything related to testing in your prod code, however by doing it this way you are making it testable. You can have your mocking library create a mock object that looks like a Random and assign it to the field. Consult your mocking framework library from this. Me, I use Spock which kills the older mocking frameworks hands down. – Bohemian Jun 28 '20 at 01:25
0

I am not sure why do need to test the outcomes of Random, the main intention should be to test whether your dice[] has the value between the specified range or not.

I would suggest you to call the method and read all the values inside dice[] and check if each value is present inside your specified range,

Range: 0 to ((HIGHEST_NUMBER_ON_DIE-LOWEST_NUMBER_ON_DIE+OFFSET) + LOWEST_NUMBER_ON_DIE)

  • I have done that. However, I would like to test my getScore() function that assigns a score based on the outcome of the dice. For that, I need to know what the dice rolled. – bluetooth12 Jun 28 '20 at 01:25
  • See, even there if your method would be "getScore" which directly calls random and get its int value, you are not testing your code, you are testing Math.Random class. I would suggest you don't have to do that. – Sabareesh Muralidharan Jun 28 '20 at 01:30