Articles NutritionCheckpoint Hangman Survey Says About Steven Cleary

Dependency Injection

Imagine building a game where a button is clicked and there is a 1/9 chance of recieving a reward.



Imagine building a game where a button is clicked and there is a 1/10000 chance of recieving a reward.



How could we test these games?

Dependency Injection

Since recieving the gem depends on Javascript's Math.random, move that dependency outside and inject it in. To do this make a RandomNumberGenerator class that returns an actual random number and pass that to the constructors of new games.

            
class Game{

  /*
    numOfPossibleOutcomes - the chance of recieving the gem is 1/numOfPossibleOutcomes
    gemHolderID - the element id of the image for the gem
  */
  constructor(numOfPossibleOutcomes, gemHolderID){
    this.numOfPossibleOutcomes = numOfPossibleOutcomes;
    this.gemHolderID = gemHolderID;
    this.reset();
  }

  /*
    removes the gem / resets the game
  */
  reset(){
    document.getElementById(this.gemHolderID).classList.add('noGem');
  }

  /*
    picks a random number, if that number is the lucky number ( 0 ), the user gets the gem
  */
  searchChest(){
    var randomNumber = Math.floor((Math.random() * this.numOfPossibleOutcomes));
    var hasNoGem = document.getElementById(this.gemHolderID).classList.contains('noGem');
    var shouldGetItem = randomNumber == 0;
    if(shouldGetItem && hasNoGem){
      document.getElementById(this.gemHolderID).classList.remove('noGem');
    }
  }

}

var easyNumberOfOccurrences = 9;
var hardNumberOfOccurrences = 10000;
var easyGemHolderID = "gemHolder";
var hardGemHolderID = "gemHolder_Hard";

var easyGame = new Game(easyNumberOfOccurrences,easyGemHolderID);
var hardGame = new Game(hardNumberOfOccurrences,hardGemHolderID);
            
          
            
class Game{

  /*
    numOfPossibleOutcomes - the chance of recieving the gem is 1/numOfPossibleOutcomes
    gemHolderID - the element id of the image for the gem
  */
constructor(numOfPossibleOutcomes, gemHolderID, rng){
    this.rng = rng;
    this.numOfPossibleOutcomes = numOfPossibleOutcomes;
    this.gemHolderID = gemHolderID;
    this.reset();
  }

  /*
    removes the gem / resets the game
  */
  reset(){
    document.getElementById(this.gemHolderID).classList.add('noGem');
  }

  /*
    picks a random number, if that number is the lucky number ( 0 ), the user gets the gem
  */
  searchChest(){
    var randomNumber = this.rng.getRandomNumber(0,this.numOfPossibleOutcomes);
    var hasNoGem = document.getElementById(this.gemHolderID).classList.contains('noGem');
    var shouldGetItem = randomNumber == 0;
    if(shouldGetItem && hasNoGem){
      document.getElementById(this.gemHolderID).classList.remove('noGem');
    }
  }

}

class RandomNumberGenerator{
  getRandomNumber(min, max){
    return Math.floor((Math.random() * max) + min);
  }
}


var easyNumberOfOccurrences = 9;
var hardNumberOfOccurrences = 10000;
var easyGemHolderID = "gemHolder";
var hardGemHolderID = "gemHolder_Hard";

var randomNumberGenerator = new RandomNumberGenerator();
var easyGame = new Game(easyNumberOfOccurrences,easyGemHolderID,randomNumberGenerator);
var hardGame = new Game(hardNumberOfOccurrences,hardGemHolderID,randomNumberGenerator);
            
          

To test, create a fake random number generator

          
  class FakeRNG{
    /*
      the random number that will always be returned by this random number generator
    */
    constructor(randomNumber){
      this.randomNumber = randomNumber;
    }
    /*
      returns the number given in the constructor
    */
    getRandomNumber(min, max){
      return this.randomNumber;
    }
  }
          
        

Then you can inject the fake random number generator into the game constructor

          
hardGame = new Game(hardNumberOfOccurrences,"gemHolder_Hard",new FakeRNG(0));
hardGame.searchChest();
          
        

Click the button and scroll up to see the gem for the hard game



This is an example of using Dependency Injection for Testing

It is important to note that by doing this there was a tradeoff. Now the game class is less encapsulated.