Search Unity

Unit testing MonoBehaviour classes that have private fields set from the inspector

Discussion in 'Editor & General Support' started by mikkomcmenamin, Dec 7, 2018.

  1. mikkomcmenamin

    mikkomcmenamin

    Joined:
    Dec 7, 2018
    Posts:
    2
    How can I set / inject values to a class in my unit test method if the values and references are set in the inspector using the [SerializeField] attribute?

    So the class "Car" to be tested might have e.g a serialized scriptable object
    Code (CSharp):
    1. [SerializeField] private CarAttributes _carAttributes;
    This could be the test method:

    Code (CSharp):
    1. [UnityTest]
    2.     public IEnumerator CarTestsWithEnumeratorPasses() {
    3.        
    4.         var carAttributes = ScriptableObject.CreateInstance<CarAttributes>();
    5.         carAttributes.Speed = 10f;
    6.        
    7.         var car = new GameObject().AddComponent<Car>();
    8.         // car._carAttributes = carAttributes; // Can't do this!!
    9.        
    10.         // Run update once
    11.         yield return null;
    12.        
    13.         Assert.That(car.transform.position.x, Is.EqualTo(<someCalculatedValue>));
    14.                    
    15.     }

    So I cannot set the private fields from the test class. Making public properties for them seems wrong, as I don't want those values to be set from anywhere else, otherwise it breaks the encapsulation. How should this be addressed?
     
    NilsMoller likes this.
  2. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    I've been letting some test information leak into the type itself, but clearly marked as such:

    Code (csharp):
    1. public class Car : MonoBehaviour {
    2.     [SerializeField] private CarAttributes _carAttributes;
    3.  
    4. #if UNITY_EDITOR
    5.     public void SetTestData(CarAttributes testAttributes) {
    6.         _carAttributes = testAttributes;
    7.     }
    8. #endif
    9. }
    A method I've considered, but not fully tested, is to use SerializedObject for this:

    Code (csharp):
    1. // test code
    2. var car = new GameObject().AddComponent<Car>();
    3.  
    4. var so = new SerializedObject(car);
    5. so.FindProperty("_carAttributes").objectReferenceValue = carAttributes;
    6. so.ApplyModifiedProperties();
    A normal unit testing rule is that if you're doing reflection to get at fields, you're not testing the real behaviour of the application, so you're doing something wrong. In this case, though, setting the fields through the SerializedProperty interface basically mocks the designer setting those fields in the inspector, so I'd say it's a good plan.

    Not sure it actually works, though.
     
    mrwellman_work likes this.
  3. mikkomcmenamin

    mikkomcmenamin

    Joined:
    Dec 7, 2018
    Posts:
    2
    Thanks for your answer @Baste ! The first option is at least descriptive and a bit more restricted because of the UNITY_EDITOR defines, so quite a solid alternative.

    I'll have to try the reflection method. Seems like a good way to achieve this. And as you said, its purpose is only to mimic setting the values and references via inspector and not really to circumvent the public interface of the class. In a way the inspector acts as the public API.