2
\$\begingroup\$

I've run into the same problem multiple times that is begging for a solution. I try to write a lot of decoupled code to make my MonoBehaviour scripts as reusable as possible. This means that I end up using events sometimes. However, when I try to unit test my code I always run into problems with events.

I usually subscribe and unsubscribe to events in OnEnable and OnDisable (sometimes Awake and OnDestroy). However, when writing my unit tests, then I can't test any behaviour dependent on such events since these Unity event functions don't get called in a unit test.

I've tried making the unity event functions public for testing purposes. But this seems to seriously break encapsulation. I've also made functions like public ListenToEvents() that does all the event subscribing, and gets called from OnEnable() and can then also be called in my unit test.

I've read in many places (and see the reasons why) you should never make something public just for unit testing purposes, but I'm struggling how to get event-related behaviour to be tested.

I know about the Humble Object Pattern, which I think could be a good solution here. However, I really like having a single script I can share with myself or someone else for reuse, and this tends to create additional classes/files.

A common example I'm having trouble testing is something like the following MonoBehaviour code (untested code)

bool ignoreInput = false;

void OnEnable() {
    KeyboardEvents.OnIgnoreAllInput += IgnoreInput;
}

void OnDisable() {
    KeyboardEvents.OnIgnoreAllInput -= IgnoreInput;
}

void IgnoreInput() {
    ignoreInput = true;
}

public void UpdateMovement() {
    if (ignoreInput) return;

    // trying to unit test this
    if (Input.GetKeyDown(Keycode.A) {
        MoveForward();
    }  
    else {
        DontMove();
    }
}

the issue with the above code, is that I can unit test MoveForward and DontMove, but I can't unit test whether they are correctly controlled by ignoreInput, since in my unit test there's no way (as written) to get OnEnable and OnDisable to be called. I'm also aware of Unity PlayMode tests, that do call such methods, but these are very clunky and slow to run and as far as I can tell can't be called from an IDE.

Ideally my unit test would be something like this:

ScriptUnderTest testScript = new GameObject().AddComponent<ScriptUnderTest>();
KeyboardEvents.IgnoreAllInput();
testScript.UpdateMovement();
Assert.That( <Some result of DontMove happened> )

This test always fails because OnEnable is not being called, and so invoking the event does nothing to the script.

I'd love know how to better set up the MonoBehaviour to make this test work.

Side Question: Do you subscribe to events in OnEnable? Is there a best practice for where in monobehaviours to do this?

\$\endgroup\$
3
  • \$\begingroup\$ Did you ever find out an answer to this? I’d be interested to know, since I work with a lot of UnityEvents as well, and would like to start testing these. \$\endgroup\$
    – MaartenS
    Mar 18, 2021 at 6:23
  • \$\begingroup\$ Have you considered building a utility function into your unit testing scripts that fetches the private OnEnable/OnDisable methods via reflection and calls them as normal? \$\endgroup\$
    – DMGregory
    Mar 18, 2021 at 11:59
  • \$\begingroup\$ I just gave up unit testing monobehaviours. It doesn’t seem to fit well. I just test my regular c# classes when needed. And try to rely on c# classes as much as possible. The idea of using reflection makes sense I guess. I’m not too familiar with reflection but sounds like it could work. But then every time you need to test a component you’ll need to remember to call that helper function to initialize the component properly. \$\endgroup\$
    – Adam B
    Mar 18, 2021 at 14:29

1 Answer 1

-1
\$\begingroup\$

The MonoBehaviour that is being tested requires the [ExecuteInEditMode] for special Unity methods to be executed in edit mode tests.

[ExecuteInEditMode]
public class SomeMonoBehaviour : MonoBehaviour {...}

This will call classes such as OnEnable() and OnDisable() methods during edit mode tests, so that subscribed events are also called as part of the test.

Note that this will also subscribe to the events when using the editor.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .