For me, unit testing was always one of the most difficult tasks in development, especially if my codebase evolved without tests in mind. The task becomes more confusing when adding Unity’s MonoBehaviours to the equation. And it gets even worse when you search Google and you only find a question without answers in Unity’s forum from 8 years ago.
There are some good introductory resources to unit testing in Unity like Jason Weimann’s Everything you need to know about Testing In Unity3D and Infallible Code’s Unit Tests in Unity. However, when I started to test more specific behavior I had some questions that Google could not answer. After some trial-and-error, I managed to successfully write a test for my trigger game object.
Testing
First, let’s declare some conditions based on Unity’s documentation:
- Both GameObjects contain a Collider component.
- One of them has “Collider.isTrigger” enabled.
- One of them contains a Rigidbody component.
Now, the trigger class. It’s a simple MonoBehaviour that has an IsTriggered field which is updated at the OnTriggerEnter and OnTriggerExit methods.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class TriggerObject : MonoBehaviour { public bool IsTriggered = false; private void OnTriggerEnter(Collider other) { isTriggered = true; } private void OnTriggerExit(Collider other) { isTriggered = false; } } |
Finally, let’s take a look at the test class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class TriggerObjectTests { [UnityTest] public IEnumerator Test_OnTriggerEnter() { var triggerGameObject = new GameObject("Trigger"); triggerGameObject.transform.position = Vector3.zero; triggerGameObject.transform.localScale = new Vector3(1.0f, 1.0f, 1.0f); var triggerObject = triggerGameObject.AddComponent(typeof(TriggerObject)) as TriggerObject; var triggerBoxCollider = triggerGameObject.AddComponent(typeof(BoxCollider)) as BoxCollider; triggerBoxCollider.isTrigger = true; var colliderGameObject = new GameObject("Collider"); colliderGameObject.AddComponent(typeof(BoxCollider)) as BoxCollider; colliderGameObject.AddComponent(typeof(RigidBody)) as RigidBody; collider.transform.position = trigger.transform.position; yield return WaitForSeconds(.5f); Assert.IsTrue(triggerObject.IsTriggered); } } |
From lines 5 to 10 I set up the Trigger game object by adding a TriggerObject and a BoxCollider. I also set the BoxCollider’s isTrigger to true to satisfy condition #2. Next, from lines 12 to 14, I set up the Collider game object by adding a BoxCollider and a Rigidbody. Now, both conditions #1 and #4 are fulfilled as well. Finally, I move the collider object to the same position as the trigger object and use yield return null to skip a frame.
Unity detects that the objects collided and calls OnTriggerEnter when the frame is skipped. In my case, this method sets IsTriggered to true and the assertion succeeds.
Conclusion
This is a simple approach that has worked for my use cases but I can’t guarantee that its the best strategy. A question that I do not yet have an answer is if test execution might be affected by framerate. It’s currently relying on skipping one frame to check if OnTriggerEnter was called. However, Unity calls OnTriggerEnter on the FixedUpdate function, which is frame-rate independent. If you already have an answer to this, please feel free to contact me so I can update this post and be a little more informed. 😀
Edit 08/07/2020: After adding some more tests, this approach started to break every now and then. I tried to use WaitForFixedUpdate which seemed to work for a while but tests started to fail again. Finally, I decided to wait for half a second using WaitForSeconds. This is working for now but I’m still not sure why WaitForFixedUpdate didn’t solve my problem.
A final note about the sample code: I used the obsolete version of AddComponent due to HTML encoding issues (I really need to look into that). Please use the generic version.