- Home /
 
How to make MonoBehavior class testable with NSubstitute?
Ton of games have been made with Unity, Hearthstone, Ori and the Blind Forest etc... and they all seem bullet-proof i.e as if they are 100% bug free. So Blizzard must have used some sort of testing for Hearthstone. I am still struggling to understand how to make a class that inherits MonoBehavior testable. You must create GameObject that will hold a MonoBehavior component.
I have this class:
 using UnityEngine;
 using System;
 using System.Runtime.Serialization.Formatters.Binary;
 using System.IO;
 
 public class SaveLoadGameData : MonoBehaviour
 {
     public static SaveLoadGameData gameState;
 
     public float experience = Helper.DEFAULT_EXPERIENCE;
     public float score = Helper.DEFAULT_SCORE;
 
     void Awake ()
     {
         Init();
     }
 
     public void Init()
     {
         if (gameState == null)
         {
             DontDestroyOnLoad(gameObject);
             gameState = this;
         }
         else if (gameState != this)
         {
             Destroy(gameObject);
         }
     }
 
     public void SaveForWeb ()
     {
         UpdateGameState();
         try
         {
             PlayerPrefs.SetFloat(Helper.EXP_KEY, experience);
             PlayerPrefs.SetFloat(Helper.SCORE_KEY, score);
 
             PlayerPrefs.Save();
         }
         catch (Exception ex)
         {
             Debug.Log(ex.Message);
         }
     }
     public void UpdateGameState ()
     {
         gameState.experience = experience;
         gameState.score = score;
     }
 }
 
 [Serializable]
 class GameData
 {
     public float experience = Helper.DEFAULT_EXPERIENCE;
     public float score = Helper.DEFAULT_SCORE;
 }
 
               I want to check if UpdateGameState() was called when calling SaveForWeb(). There is a tutorial how to do that with NSubstitute here: http://nsubstitute.github.io/help/received-calls/ But it uses Interface and a class constructor that recieves interface.
how would you make my class testable with NSubstitute? Should SaveLoadGameData implement ISaveLoadGameData interface or I create constructor that calls a function inside?
Let me know your ideas.
Answer by spinnerbox · Mar 03, 2016 at 01:12 PM
According to the tutorial for testing MonoBehaviors I made decoupling of the MonoBehavior functionality and the other testable functionality using separate class and an Interface
 using System;
 using UnityEngine;
 
 namespace Assets.Scripts
 {
     /// <summary>
     /// Description of ISaveLoadGameData.
     /// </summary>
     public interface ISaveLoadGameData
     {
         void Init();
         void SaveForWeb();
         void UpdateGameState();
     }
 }
 
 using System;
 using UnityEngine;
 
 namespace Assets.Scripts
 {
     /// <summary>
     /// Description of SaveLoadGameDataController.
     /// </summary>
     [Serializable]
     public class SaveLoadGameDataController : ISaveLoadGameData
     {
         ISaveLoadGameData slgdInterface;
         GameObject gameObject;
 
         public static SaveLoadGameDataController gameState;
 
         public float experience = Helper.DEFAULT_EXPERIENCE;
         public float score = Helper.DEFAULT_SCORE;
 
         public void SetSaveLoadGameData (ISaveLoadGameData slgd)
         {
             slgdInterface = slgd;
         }
         public Init ()
         {
               slgdInterface.Init();
         }
 
         public void SaveForWeb ()
         {
             slgdInterface.SaveForWeb();
         }
         public void UpdateGameState ()
         {
             slgdInterface.UpdateGameState();
         }
     }
 }
 
               This way I was able to make clean and simple tests for Save() function like this:
     [Test]
     [Category(Helper.TEST_CATEGORY_SAVE_FOR_WEB)]
     public void SaveForWebTest_CreateFakeGameStateObjectRunTheFuncAndCheckIfUpdateGameStateIsCalled_PassesIfUpdateGameStateFuncWasCalled ()
     {
         // arrange
         var slgdController = FakeSaveLoadGameDataController();
             
         // act
         slgdController.ClearReceivedCalls();
         slgdController.SaveForWeb();
         
         // assert
         slgdController.Received().UpdateGameState();
     }
 
               Where FakeSaveLoadGameDataController() looks like this:
 SaveLoadGameDataController FakeSaveLoadGameDataController ()
 {
     SaveLoadGameDataController slgdController = Substitute.For<SaveLoadGameDataController>();
     ISaveLoadGameData slgd = Substitute.For<ISaveLoadGameData>();
     slgdController.SetSaveLoadGameData(slgd);
 
     slgdController.experience = Arg.Is<float>(x => x > 0);
     slgdController.score = Arg.Is<float>(x => x > 0);
 
     return slgdController;
 }
 
              Answer by phil_me_up · Mar 02, 2016 at 01:53 PM
Have you seen the blog post on the Unity Test Tools: http://blogs.unity3d.com/2014/07/28/unit-testing-at-the-speed-of-light-with-unity-test-tools/
In there they give some examples of how you might achieve this, specifically in the Mock object section.
EDIT: Meant to link to this too, which specifically deals with the problems of testing Monobehaviours: http://blogs.unity3d.com/2014/06/03/unit-testing-part-2-unit-testing-monobehaviours/
Yes the tutorials leaded me to the solution but I cannot accept this as an answer. I will post my own how I solved this.
Your answer
 
             Follow this Question
Related Questions
NUnit difference between Assert's IsTrue/IsFalse and True/False 1 Answer
Wait times when developing 3 Answers
Can Unity 5.6's Test Runner optionally output NUnit 2 xml format? 2 Answers
How can I run Unity tests with some defined nUnit category 2 Answers
How to implement mandatory cleanup for the Unity PlayMode tests? 1 Answer