- Home /
Creating Test Doubles for Unity3D Objects
After making my classes collaborate passively (i.e. objects that the class uses are passed in at instantiation) so that I could unit test them, I realized that I cannot create test doubles (e.g. test stubs) for UnityEngine objects (such as Touch) that are passed into the class under testing (see NSubstitute Docs). As stated in the NSubstitute documentation, substitution only works with interfaces, or objects' virtual/abstract members.
I wanted to pass in a Touch with Touch.phase = TouchPhase.Began, and Touch.position = new Vector2(1.0f, 1.0f) to the class under testing, which does something with the Touch object. Because Touch.phase and Touch.position are not virtual/abstract, I cannot substitute for them and set a return value with NSubstitute. Of course, they are read-only fields as well, so I cannot set their values at instantiation.
Is it not possible to unit test methods that consume UnityEngine objects? If it is, what are my option(s) in doing so, and how does one go about in performing such option(s)?
Answer by Statement · Apr 17, 2016 at 12:34 PM
I recently tested input logic, although I was testing code that was using UnityEngines EventSystem functionality. Regardless, the same problem we both get is that the code becomes untestable due to depending on runtime user input.
What I did was decouple the logic code from the input types. This creates a class that contains logic, that is testable and then my component class feeds the logic the data from the input. If you need more data, consider making a class or struct and pass that into your methods.
public class TestableLogic // tested
{
public void OnDown(int pointerId, Vector2 position) { ... }
}
public class UntestableInputLogic : MonoBehaviour, IPointerDownHandler // not tested
{
TestableLogic t = new TestableLogic();
public void OnPointerDown(EventSystems.PointerEventData eventData)
{
t.OnDown(eventData.pointerId, eventData.position); // pass the data I need
}
}
It might be possible to actually test the code creating a PointerEventData, but it required an EventSystem in the constructor and I decided to just keep it simple in the face of unknowns at the time.
Answer by BoraHorzaGobuchul · Apr 18, 2016 at 01:30 PM
@OperationT: As @Statement illustrates, the Unity3D api is easy to use but usually impossible to unit test. I have tried to apply Test Driven Development (TDD) whenever I can in Unity3D, but sometimes it is just not worth the stress. So with a simple FPS with little logic, I just have manual tests. But ... if I start to develop a game with a significant amount of logic/AI (e.g. simulations like Conway's Game Of Life) then I will ignore Unity and create the AI without any reference to any UI at all. This turns the problem the right way up - not 'how do adapt my code to Unity' but 'how do I adapt Unity to my code' ? This way you can easily and safely modify the AI as you add each new feature - TDD is more about flexible design than functional testing. For ideas on how to deal with difficult-to-test APIs, have a look at Micheal Feathers' "Working Effectively With Legacy Code", chapter "My Application Is All API Calls".