OnEnable() not called for objects instantiated in Unity Tests

I am a little confused by the Unity Test Framework.
I have developed a few tests, and if I use game objects, I can instantiate them but they never get enabled.

using UnityEngine;

public class Buddy : MonoBehaviour
{
    private SpriteRenderer spriteBuddy;

    public Color GetColor()
    {
        return spriteBuddy.Color;

    }

    private OnEnable()
    {
        spriteBuddy = GetComponent<SpriteRenderer>();
    }   
}
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;

public class ProjectTests
{
    [Test]
    public void CollisionRadarTests()
    {
        Buddy buddy = MonoBehaviour.Instantiate(Resources.Load<Buddy>("BuddyPrefab"));
        Assert.AreEqual(buddy.GetColor(), Color.White);
    }

The problem is that the function OnEnable() of the instantiated object is never called when running the Test (it is called fine when running the game), so spriteBuddy is not defined when running the Test, and therefore the test triggers a Null exception when calling GetColor().

Suggestions?

Nope!

You have to use GameObject.Instantiate() ideally with the Type params. You can’t just instantiate or new a MonoBehaviour, it needs to be added to a GameObject.

Uhm, neither of those exist ^^. Instantiate is a static method in the UnityEngine.Object class. As a result every derived class has it available.

grafik

So it doesn’t matter what class you use, it will always be UnityEngine.Object.Instantiate.

Also you can Instantiate a MonoBehaviour (from a prefab or other instance) since Unity will automatically clone the gameobject with all components and all nested gameobjects. When you instantiate a component, you automatically get back the cloned component reference.

Though back on topic, I don’t know what might be the reason, I haven’t really used the testing system yet.

Haha, you’re absolutely right. :slight_smile:

I’ve never seen MonoBehavior.Instantiate used anywhere before.

But now that you pointed this out I think I see the actual issue:

This should be a [UnityTest] and for the event methods to run I think you need to yield once:

    [UnityTest]
    public IEnumerator CollisionRadarTests()
    {
        Buddy buddy = Instantiate(Resources.Load<Buddy>("BuddyPrefab"));
        yield return null;
        Assert.AreEqual(buddy.GetColor(), Color.White);
    }

Also, if at all possible, you should avoid putting anything under Resources unless you absolutely have to. Because everything in Resources is included in a build whether used or not, and yes, this goes for test-only resources too!

In editor tests you can use AssetDatabase.LoadAsset to load assets with their path.

But for the most part you should even avoid doing that. Instead test the components in isolation. Here, you can and should create a GameObject with your Buddy script on it as well as a SpriteRenderer component.

The more you test your components in an isolated manner like this, the better because then your tests don’t rely on any “magic” in the prefab, instead the test actually defines what the script requires to function correctly. This is what a unit test should be: self-contained.

If you test an entire prefab works as you expect it to, you’re actually creating an integration test which also typically run longer and don’t provide you the benefits of fine-grained unit tests. As in: smallest possible unit of work ie verify that the Buddy script has obtained a non-null reference to the SpriteRenderer component, not that the SpriteRenderer’s default color is White because for all you know that could have been changed in the Inspector to (254,255,255) which likely wouldn’t even make a difference in the game. Also forcing you to adapt the test any time you change Buddy’s default color. That’s besides the point of a unit test.

1 Like

Ok thank you.
I have changed the way I instantiate: Object.Instantiate(prefab, transform). I have changed the test function attribute from [Test] to [UnityTest] and used yield return null after instantiation.
The OnEnable() function is still not called when running the test. I suspect it is only called if the object is effectively in an active scene. But then I wonder how to get an active scene when running tests.

If I recall correctly running tests will use a temporary scene so as to not pollute the actual editor scene with test objects.

Resources Load returns non null? And the prefab that gets loaded has the component set to enabled in the Inspector and the root game object is active?

1 Like

Just to be clear, you’re running a Play mode test, not an edit mode test, right?

I am running the test in Edit Mode. The prefab returns non null, the object gets instantiated, all the test code runs well except OnEnable() is never called. If I turn OnEnable() to a public method and call it explicitely from the test code after instantiation, the object behaves normally (I have access to the SpriteRenderer component, etc).

Now if I try to put my test code in Play Mode, I am confused. When I run the test cases from the Test Runner, it does launch my game, but the test code doesn’t even start.

The documentation is not very clear on this, but edit mode tests are meant for editor stuff, they don’t run OnEnable, unless you call them manually. It looks like you want to test gameplay logic, so you have to use a play mode test instead, then OnEnable etc will be called as expected.

2 Likes