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.

Have you seen the blog post on the Unity Test Tools: Unit testing at the speed of light with Unity Test Tools | Unity Blog

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: Unit testing part 2 - Unit testing MonoBehaviours | Unity Blog

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;
}