4
\$\begingroup\$

I've just started using unity tests with the built in editor tests. After making my test and finding out that it constantly failed I debugged the code and found that a static object was set to a new game object when running the test while it is set to null when playing in the editor.

Is there a way to force unity to treat the test the in the same way that it treats scripts at run time?

Edit

After changing some code around i get another strange error: Editor Tests window showing error

does anyone know why "null" isn't "null". The null objects are stored in an array, but that shouldn't make a difference right?

Edit Edit (Edit 2?)

Here is the code that has the issue

public class StatCollector : MonoBehaviour {

    public static StatCollector running;

    //ensure that there is only one object running at the same time
    public void Awake () {
        if(running == null)
        {
            DontDestroyOnLoad(gameObject);
            running = this;
        }
        else if(running != this)
        {
            DestroyImmediate(gameObject);
        }
    }
}

and here is the test:

[Test]
public void SingleTest()
{
    //create gameobjects
    GameObject[] collect = new GameObject[2];
    collect[0] = new GameObject("one");
    collect[1] = new GameObject("two");
    collect[0].AddComponent<StatCollector>();

    //see if gameobjects were created correctly
    if (collect[0] == null || collect[1] == null) Assert.Fail("failed to create game objects");

    //test what happens when the second stat collector is added to the scene
    collect[1].AddComponent<StatCollector>();
    collect[0].GetComponent<StatCollector>().Awake();
    collect[1].GetComponent<StatCollector>().Awake();
    Assert.IsNull(collect[1]);
}

I'm assuming that there is no way to keep Awake private which is why it is public (just so that the test case has access to it)

\$\endgroup\$
  • \$\begingroup\$ can you share your code for the test, it might make things easier \$\endgroup\$ – Matthew Pigram Jan 28 '16 at 1:49
  • \$\begingroup\$ @DMGregory I think that could be the case but there are no frames in a test so how would one emulate that? \$\endgroup\$ – user3797758 Jan 28 '16 at 15:11
  • \$\begingroup\$ If you replace Assert.IsNull(collect[1]) with Assert.IsTrue(collect[1] == null) do you get the same result? It's possible IsNull is using the standard comparison instead of the overridden one. \$\endgroup\$ – DMGregory Jan 28 '16 at 15:16
  • \$\begingroup\$ Yeah that solves it, post it as an answer so that I can choose it as an answer :) \$\endgroup\$ – user3797758 Jan 28 '16 at 15:28
8
\$\begingroup\$

First some background...

There are two flavours of "null" in Unity:

  • Actual null

  • "Pseudo-Null" - this is a non-null Unity object (usually a GameObject or MonoBehaviour) that has been removed from the scene using Destroy or unloading a scene.

    (In-editor, this is also used for reference fields in an Inspector that have been left blank)

    The reason this is called pseudo-null is that for an object in this state, (destroyedObject == null) evaluates to true using an overloaded operator.

Using DestroyImmediate() should put an object into this state immediately, while using Destroy() will do so at the end of the current frame's updates but before rendering, which can often help avoid order-of-execution bugs when multiple scripts are referencing an object.

The thing is that a "pseudo-null" reference isn't actually null, which you can confirm by calling:

System.Object.ReferenceEquals(destroyedObject, null); // returns false

...or by trying to access any of the object's members - you won't get a null reference exception, but a Unity MissingReferenceException (which is handy, because the debugging tools can tell you more about which object got destroyed while a script was still trying to use it - something we don't get from real nulls).

This psuedo-null stub will remain until all references to it are cleared/go out of scope, using normal C# garbage collection rules. Read more about the reasons this exists in this Unity blog post.


How this applies to your test

My guess is that Assert.IsNull() at the end of your test is internally checking for real null instead of comparing with the overloaded operator.

(Especially if you're using Microsoft.VisualStudio.TestTools.UnitTesting.Assert rather than Unity's version - I don't see any using statements in your code so I'm guessing)

Since the destroyed object is only pseudo-null, this check comes back false, tripping the assertion.

We can ensure it uses the overloaded operator to also detect pseudo-null by rephrasing the assert at the end of your test as:

Assert.IsTrue(collect[1] == null);

We know this == will return true for a Destroy()ed object, whether it's recently-destroyed or long-dead, so this phrasing reliably checks for what we want.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.