Search Unity

How to unit test UnityEvent?

Discussion in 'Testing & Automation' started by Saucyminator, May 14, 2022.

  1. Saucyminator

    Saucyminator

    Joined:
    Nov 23, 2015
    Posts:
    61
    Hi. I'm trying to expand and improve my current HealthSystem I got in place, I want to test if UnitEvents are being called but I can't find any information on how to unit test UnitEvents.

    Here's my code so far that fails, expected result should be true but actual are false.

    I'm using NSubstitute.

    Code (CSharp):
    1. // The test
    2. [Test]
    3. public void Checks_OnValueChanged_Event_Was_Called () {
    4.   INewDamageable<int> _damageable = Substitute.For<INewDamageable<int>>();
    5.   bool _wasCalled = false;
    6.  
    7.   _damageable.OnValueChanged?.AddListener(() => _wasCalled = true);
    8.   _damageable.TakeDamage(1, null);
    9.  
    10.   Assert.IsTrue(_wasCalled);
    11. }
    12.  
    13. // Interface
    14. public interface INewDamageable<T> {
    15.   void TakeDamage (T _amount, DataDamageType _damageType);
    16.  
    17.   UnityEvent OnValueChanged { get; }
    18. }
    19.  
    20. // Implementation
    21. public class NewDamageable : MonoBehaviour, INewDamageable<int> {
    22.   public void TakeDamage (int _amount, DataDamageType _damageType) {
    23.     healthSystem.Damage(_amount);
    24.  
    25.     OnValueChanged?.Invoke();
    26.   }
    27.  
    28.   public UnityEvent OnValueChanged { get; } = new UnityEvent();
    29. }
     
    akuno likes this.
  2. Pogudesu

    Pogudesu

    Joined:
    Aug 3, 2019
    Posts:
    2
    I'm also interested in this issue. Does anyone have a solution for this?
     
  3. rboonzaijer

    rboonzaijer

    Joined:
    Jan 10, 2020
    Posts:
    1
    Hi,

    I came here with the same question, for me the solution below is working (TestRunner in EditMode) and that looks very much like your setup, but I'm not using Substitute, so perhaps the issue could be with that?

    TestHealth.cs

    Code (CSharp):
    1. public class TestHealth
    2. {
    3.     [Test]
    4.     public void OnChangeHealth()
    5.     {
    6.         // Arrange
    7.         Health health = (new GameObject()).AddComponent<Health>();
    8.         health.SetMaxHealth(100);
    9.         health.SetCurrentHealth(80);
    10.  
    11.         List<IntChange> invokes = new List<IntChange>();
    12.         health.OnHealthChanged.AddListener((IntChange change) =>
    13.         {
    14.             invokes.Add(change);
    15.         });
    16.  
    17.  
    18.         // Test
    19.         health.SetCurrentHealth(50);
    20.         health.SetCurrentHealth(99);
    21.         health.SetCurrentHealth(0);
    22.  
    23.  
    24.         // Assert
    25.         Assert.AreEqual(3, invokes.Count); // Event should have been called 3 times
    26.  
    27.         Assert.AreEqual(80, invokes[0].OldValue);
    28.         Assert.AreEqual(50, invokes[0].NewValue);
    29.  
    30.         Assert.AreEqual(50, invokes[1].OldValue);
    31.         Assert.AreEqual(99, invokes[1].NewValue);
    32.  
    33.         Assert.AreEqual(99, invokes[2].OldValue);
    34.         Assert.AreEqual(0, invokes[2].NewValue);
    35.     }
    36.  
    37.     [Test]
    38.     public void OnDie()
    39.     {
    40.         Health health = (new GameObject()).AddComponent<Health>();
    41.         health.SetMaxHealth(100);
    42.         health.SetCurrentHealth(80);
    43.  
    44.         int invokes = 0;
    45.         health.OnDie.AddListener(() =>
    46.         {
    47.             invokes++;
    48.         });
    49.  
    50.         health.SetCurrentHealth(0);
    51.  
    52.         Assert.AreEqual(1, invokes); // Event should have been called 1 time
    53.     }
    54. }

    Health.cs

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Events;
    3.  
    4. public class Health : MonoBehaviour
    5. {
    6.     private int _maxHealth = 1;
    7.     private int _currentHealth = 1;
    8.  
    9.     public UnityEventIntChange OnHealthChanged { get; } = new UnityEventIntChange();
    10.     public UnityEvent OnDie { get; } = new UnityEvent();
    11.  
    12.     // also works without { get; }, the events will then be visible in the Inspector
    13.     //     public UnityEventIntChange OnHealthChanged = new UnityEventIntChange();
    14.     //     public UnityEvent OnDie = new UnityEvent();
    15.  
    16. /* ... */
    17.  
    18.     public void SetCurrentHealth(int amount)
    19.     {
    20.         IntChange change = new IntChange() { OldValue = _currentHealth };
    21.  
    22.         _currentHealth = Mathf.Clamp(amount, 0, _maxHealth);
    23.  
    24.         change.NewValue = _currentHealth;
    25.         OnHealthChanged?.Invoke(change);
    26.        
    27.         if(_currentHealth == 0)
    28.         {
    29.             OnDie?.Invoke();
    30.         }
    31.     }
    32.  
    33.     public void IncreaseHealth(int amount)
    34.     {
    35.         SetCurrentHealth(_currentHealth + amount);
    36.     }
    37.  
    38.     public void DecreaseHealth(int amount)
    39.     {
    40.         SetCurrentHealth(_currentHealth - amount);
    41.     }
    42. }

    IntChange.cs

    Code (CSharp):
    1. public class IntChange
    2. {
    3.     public int NewValue;
    4.     public int OldValue;
    5.  
    6.     public int Difference
    7.     {
    8.         get => NewValue - OldValue;
    9.     }
    10. }

    UnityEventIntChange.cs

    Code (CSharp):
    1. using System;
    2. using UnityEngine.Events;
    3.  
    4. [Serializable]
    5. public class UnityEventIntChange : UnityEvent<IntChange> { }