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?